/*
 * AppState.h
 *
 *  Created on: 2009-07-06
 *      Author: wasyl
 */

#ifndef APPSTATE_H_
#define APPSTATE_H_

/***********************************************************************/

#include <allegro5/allegro.h>
#include "AppStateBase.h"
#include "../time/DeltaTime.h"
#include "../input/Switch.h"

/***********************************************************************/

enum FinishedReason {FR_RUNNING = 0, FR_RESULT, FR_USER_QUIT};

/***********************************************************************/

template <class ResultType>
class AppState : public AppStateBase
{
private:
	ResultType mResult;
	FinishedReason mFinishedReason;

	AppStateBase *mParent;
	DeltaTime *mTiming;

	ALLEGRO_EVENT_QUEUE *mEventQueue;
	ALLEGRO_DISPLAY *mDisplay;
	ALLEGRO_KEYBOARD_STATE mKeyState;
	ALLEGRO_MOUSE_STATE mMouseState;

	/**
	 * Internal main loop of every application state.
	 */
	void run(AppStateBase *parent)
	{
		mParent = parent;
		mFinishedReason = FR_RUNNING;
		sCurrent = this;

		mTiming->restart();
		while (!mFinishedReason)
		{
			handleEvents();
			updateInputs();

			DeltaInterval interval = mTiming->delta();
			if (interval.dt > 0.0)
				update(interval.dt);
			if (mFinishedReason)
				break;

			if (!interval.skipRendering)
				renderFrame();
		}
		mTiming->stop();
		sCurrent = mParent;
	}

protected:
	/**
	 * An AppState subclass calls this from within its update() or event handling
	 * methods to finish the operation of the app state and return a result.
	 */
	void finish(ResultType result)
	{
		mResult = result;
		mFinishedReason = FR_RESULT;
	}

	void finish(FinishedReason reason)
	{
		mFinishedReason = reason;
	}

public:
	/**
	 * Creates a new appstate bound to a specific display.
	 */
	AppState(ALLEGRO_DISPLAY *display)
	{
		mDisplay = display;
		mFinishedReason = FR_RUNNING;
		mParent = NULL;

		mEventQueue = al_create_event_queue();
		al_register_event_source(mEventQueue, (ALLEGRO_EVENT_SOURCE*)display);

		mTiming = new DeltaTime();
	}

	/**
	 * Creates a new appstate bound to a specific display,
	 * with a non-standard timing object.
	 */
	AppState(ALLEGRO_DISPLAY *display, DeltaTime *timing)
	{
		mDisplay = display;
		mFinishedReason = FR_RUNNING;
		mParent = NULL;

		mEventQueue = al_create_event_queue();
		al_register_event_source(mEventQueue, (ALLEGRO_EVENT_SOURCE*)display);

		mTiming = timing;
	}

	/**
	 * Destroys the appstate.
	 */
	virtual ~AppState()
	{
		al_destroy_event_queue(mEventQueue);
		delete mTiming;
	}

	/**
	 * Spawns a child application state, passes control to it, waits for it to finish operation
	 * and returns its result.
	 */
	template <class R> R spawn(AppState<R> *childState)
	{
		// run child
		childState->run(this);
		// drop all events
		while (al_drop_next_event(mEventQueue));
		// restart delta timing
		mTiming->restart();

		// bubble-up quit
		if (childState->finishedReason() == FR_USER_QUIT)
			if (quitRequested())
				finish(FR_USER_QUIT);

		// return result
		return childState->result();
	}

	/**
	 * Runs as the main state.
	 */
	ResultType runAsMain()
	{
		this->run(NULL);
		return result();
	}

	/**
	 * Returns the result of this appstate. The result is set by the finish() method.
	 */
	ResultType result() { return mResult; }

	/**
	 * Returns the reason the state was finished - if it is finished.
	 */
	FinishedReason finishedReason() { return mFinishedReason; }

	/**
	 * Event-handling part of the main loop.
	 */
	virtual void handleEvents()
	{
		ALLEGRO_EVENT event;
		while(al_get_next_event(mEventQueue, &event))
			handleEvent(&event);
	}

	/**
	 * Updates all the inputs.
	 */
	virtual void updateInputs()
	{
		al_get_keyboard_state(&mKeyState);
		al_get_mouse_state(&mMouseState);
		Switch::updatePrimaries();
	}

	/**
	 * Handles one specific event.
	 */
	virtual void handleEvent(ALLEGRO_EVENT *event)
	{
		switch(event->type)
		{
		case ALLEGRO_EVENT_DISPLAY_CLOSE:
			if (quitRequested())
				finish(FR_USER_QUIT);
			break;
		}
	}

	/**
	 * Fully renders one frame.
	 */
	void renderFrame()
	{
		al_set_current_display(mDisplay);
		al_clear_to_color(al_map_rgb(0,0,0));

		render(mDisplay);

		al_flip_display();
	}

	/**
	 * Logic-step for the appstate.
	 */
	virtual void update(double dt){};

	/**
	 * Render a single frame of the appstate.
	 */
	virtual void render(ALLEGRO_DISPLAY *display){};

	/**
	 * Reaction to a quit request by the user. Should return true if the state agrees to being finished and closed,
	 * false if the state handles it in its own specific way.
	 */
	virtual bool quitRequested(){return true;}

	/**
	 * Returns the parent state that spawned this one.
	 */
	AppStateBase *parent() { return mParent; }

	/**
	 * Returns the display used by the state.
	 */
	ALLEGRO_DISPLAY *display() { return mDisplay; }

	/**
	 * Returns the keyboard state as of current frame.
	 */
	ALLEGRO_KEYBOARD_STATE *keyState() { return &mKeyState; }

	/**
	 * Returns the mouse state as of current frame.
	 */
	ALLEGRO_MOUSE_STATE *mouseState() { return &mMouseState; }
};

#endif /* APPSTATE_H_ */
