#pragma once
#include <SFML/Graphics.hpp>


class SfEventPublisher;

class SfEventSubscriber {
public:
	virtual ~SfEventSubscriber() {};
	virtual void update(SfEventPublisher* p, sf::Event e) = 0;
};

class SfEventPublisher {
public:

	// Allows the subscription to classes of events
	enum Event_Class {
		JoystickButtonEvent,
		JoystickConnectEvent,
		JoystickMoveEvent,
		KeyEvent,
		MouseButtonEvent,
		MouseMoveEvent,
		MouseWheelEvent,
		MouseWheelScrollEvent,
		SensorEvent,
		SizeEvent,
		TextEvent,
		TouchEvent,
		WindowEvent
	};

	virtual ~SfEventPublisher() {};
	virtual void subscribe(SfEventSubscriber *s, Event_Class c) {
		subscribers[c].push_back(s);
	};
	virtual void unsubscribe(SfEventSubscriber *s, Event_Class c) {
		std::remove(subscribers[c].begin(), subscribers[c].end(), s);
	};
	virtual void notify(sf::Event e) {

		// I don't quite like that some event classes contained multiple types of events:
		// KeyPressed, and KeyReleased under KeyEvent for example.
		// While others are not represented by classes:
		// Closed, LostFocus, and GainedFocus have no representing class.
		// So, we'll add another "class", WindowEvent which contains those previously mentioned,
		// and use those new identifiers to group our events

		// This will also make it a bit easier when subscribing to certain events

		Event_Class event_class;

		if (e.type == sf::Event::Closed      ||
			e.type == sf::Event::LostFocus   ||
			e.type == sf::Event::GainedFocus ){

			event_class = Event_Class::WindowEvent;
		}

		// Preserve a little of sfml's default behavior and separate resized event
		else if (e.type == sf::Event::Resized) {
			event_class = Event_Class::SizeEvent;
		}

		else if (e.type == sf::Event::TextEntered) {
			event_class = Event_Class::TextEvent;
		}

		else if (e.type == sf::Event::KeyPressed  ||
				 e.type == sf::Event::KeyReleased ){

			event_class = Event_Class::KeyEvent;
		}

		else if (e.type == sf::Event::MouseWheelMoved    ||
				 e.type == sf::Event::MouseWheelScrolled ){

			event_class = Event_Class::MouseWheelScrollEvent;
		}

		else if (e.type == sf::Event::MouseButtonPressed  ||
				 e.type == sf::Event::MouseButtonReleased ){

			event_class = Event_Class::MouseButtonEvent;
		}

		// Is this a good idea, mixing events that contain data, and don't contain data?
		else if (e.type == sf::Event::MouseMoved   ||
				 e.type == sf::Event::MouseEntered ||
				 e.type == sf::Event::MouseLeft    ){

			event_class = Event_Class::MouseMoveEvent;
		}

		else if (e.type == sf::Event::JoystickButtonPressed  ||
				 e.type == sf::Event::JoystickButtonReleased ){

			event_class = Event_Class::JoystickButtonEvent;
		}

		else if (e.type == sf::Event::JoystickMoved) {
			event_class = Event_Class::JoystickMoveEvent;
		}

		else if (e.type == sf::Event::JoystickConnected    ||
				 e.type == sf::Event::JoystickDisconnected ){

			event_class = Event_Class::JoystickConnectEvent;
		}

		else if (e.type == sf::Event::TouchBegan ||
				 e.type == sf::Event::TouchEnded ||
				 e.type == sf::Event::TouchMoved ){
			
			event_class = Event_Class::TouchEvent;
		}

		else if (e.type == sf::Event::SensorChanged) {
			event_class = Event_Class::SensorEvent;
		}

		else {
			std::cout << "Unable to classify sf::Event into Event_Class";
			abort();
		}

		// get the bucket containing subscribers to that Event_Class
		std::vector<SfEventSubscriber*> *event_type_bucket = &subscribers[event_class];

		// Send them the event
		for (auto s : *event_type_bucket) {
			s->update(this, e);
		}
	};
private:
	std::map<Event_Class, std::vector<SfEventSubscriber*>> subscribers;

};

