#pragma once

#include "stdafx.h"
#include "tetris.h"
#include "DGLE2.h"

#pragma warning(push)
#pragma warning(disable: 4290)

template<typename T>
inline char *Ttoa(T i, char *str, int radix);

template<>
inline char *Ttoa(int i, char *str, int radix)
{
	return itoa(i, str, radix);
}

template<>
inline char *Ttoa(long i, char *str, int radix)
{
	return _ltoa(i, str, radix);
}

template<>
inline char *Ttoa(unsigned long i, char *str, int radix)
{
	return _ultoa(i, str, radix);
}

template<typename T>
inline std::string IntToStr(T i, int radix = 10)
{
	char str[std::numeric_limits<T>::digits10 + 1];// +1 for termination 0
	return Ttoa(i, str, radix);
}

class CException: private std::exception
{
public:
	CException(const char *str, HRESULT hr): exception(str), _hr(hr) {}
	void operator ()() const
	{
		MessageBoxA(NULL, (std::string(what()) + ": hr == " + IntToStr(_hr)).c_str(), "Error", MB_ICONERROR);
	}
private:
	const HRESULT _hr;
};

template<class F, class C>
class CCallback
{
public:
	CCallback(const F &f, C &object): _f(f), _object(object)
	{
	}
	void operator ()() const
	{
		_f(_object);
	}
	~CCallback()
	{
	}
private:
	const F _f;
	C &_object;
};

template<class F, class C>
class CConstCallback
{
public:
	CConstCallback(const F &f, const C &object): _f(f), _object(object) {}
	void operator ()() const
	{
		_f(_object);
	}
private:
	const F _f;
	const C &_object;
};

template<class F, class C>
inline CCallback<F, C> CreateCallback(const F &f, C &object)
{
	return CCallback<F, C>(f, object);
}

template<class F, class C>
inline CConstCallback<F, C> CreateConstCallback(const F &f, const C &object)
{
	return CConstCallback<F, C>(f, object);
}

struct TMouseState
{
	enum E_BUTTON_STATE
	{
		BS_DOWN,
		BS_UP,
		BS_NONE
	};
	TMouseState(E_BUTTON_STATE buttonState, int x, int y): buttonState(buttonState), x(x), y(y) {}
	const E_BUTTON_STATE buttonState;
	const int x, y;
};

template<typename F>
class CButton
{
public:
	CButton(int x0, int y0, int x1, int y1, const F &callback);
	void Process(const TMouseState &mouse_state);
private:
	bool Inside(int x, int y) const;
	const F _callback;
	const int _x0, _y0, _x1, _y1;
	bool _pressed;
};

class CGame
{
public:
	CGame(DGLE2::IEngineCore &engineCore) throw(CException);
	void operator ()() throw(CException);
private:
	enum E_STATE
	{
		S_MAIN_MENU,
		S_NAME_PROMPT,
		S_TETRIS,
		S_RECORDS,
		S_CREDITS
	};

	TMouseState MouseState() const;
	template<void (CGame::*callback)()>
	static void CALLBACK Callback(void *game) throw();
	void Init();
	void Free();
	void MainMenuRender();
	void MainMenuProcess();
	void NamePromptRender();
	void NamePromptProcess();
	void TetrisRender();
	void TetrisProcess();
	void RecordsRender();
	void RecordsProcess();
	void CreditsRender();
	void CreditsProcess();
	void SetState(E_STATE state);
	void Exit() const;
	CTetris _tetris;
	DGLE2::IEngineCore &_engineCore;
	DGLE2::IRender *_render;
	DGLE2::IRender2D *_render2D;
	DGLE2::IInput *_input;
	unsigned int _rightDuration, _leftDuration, _cwDuration, _ccwDuration;
	static const unsigned int _shiftDelay = 2, _rotateDelay = 4;

	DWORD _startTickCount, _driverTickCount;
	static const DWORD _levelDuration = 3000 * 60;
	static const unsigned int _driverDuration = 15000;
	unsigned int _curLevel, _curDriverImage, _curMessage;

	char _name[27];

	// resources
	DGLE2::IResourceManager *_resMan;
	DGLE2::ITexture *_mainMenuImage, *_namePrompt, *_cars[7], *_backgrounds[4], *_recordsImage, *_credits, *_driverImages[16], *_messages[8];
	DGLE2::IMusic *_music;
	DGLE2::IBitmapFont *_font;

	typedef std::map<std::string, unsigned int> TRecords;
	static const size_t _maxRecords = 10;
	TRecords _records;

	// buttons
	mutable bool _mousePressed;
	bool _enableBoost;

	// main menu
	CButton<CCallback<std::binder2nd<std::mem_fun1_ref_t<void, CGame, E_STATE>>, CGame>> _newGameButton, _recordsButton, _creditsButton;
	CButton<CConstCallback<std::const_mem_fun_ref_t<void, CGame>, CGame>> _exitButton;

	// records
	CButton<CCallback<std::binder2nd<std::mem_fun1_ref_t<void, CGame, E_STATE>>, CGame>> _recordsMainMenuButton;

	// credits
	CButton<CCallback<std::binder2nd<std::mem_fun1_ref_t<void, CGame, E_STATE>>, CGame>> _creditsMainMenuButton;
};

#pragma warning(pop)