//	LuaGameState.cpp - Basic GameState which does everything via Lua.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2013  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "sound/GenericFileSounder.h"
#include "drawing/Drawer.h"
#include "LuaGameState.h"
#include "LuaClasses.h"
#include "LuaDrawer.h"
#include "FilePath.h"
#include "UTF8File.h"

#include "luabind/luabind.hpp"

//------------------------------------------------------------------------------
GameStateCreator<LuaGameState> LuaGameState::creator;

//------------------------------------------------------------------------------
LuaGameState::LuaGameState(Application& app, shared_ptr<Sounder>& sound):
GameState(app, sound),
luaState(0),
fade(1.0f),
fadeTween(fade, 0.0f, 0.01f),
logFile(L"NiallsAVLibLuaLog"),
luaError(false),
sounder(dynamic_pointer_cast<GenericFileSounder>(sound))
{
	std::string luaFilePath;
#ifdef OSX
	FilePath scriptsDir(FilePath::getCurrentExecutableDir().getChild(L"scripts"));
#else
	FilePath scriptsDir(FilePath::getDataFilesDir().getChild(L"scripts"));
#endif
	FilePath luaFile(scriptsDir);

	try
	{
		luaState = luaL_newstate();
		luaL_openlibs(luaState);
		luabind::open(luaState);

		LuaClasses::registerLuaClasses(luaState);
		LuaDrawer::registerLuaMethods(luaState);

		//Register this class' sound methods.
		luabind::module(luaState)
		[
			luabind::class_<LuaGameState>("LuaGameState")
				.def("playMusic", &LuaGameState::playMusic)
				.def("stopMusic", &LuaGameState::stopMusic)
				.def("triggerSound", &LuaGameState::triggerSound)
				.def("triggerLoop", &LuaGameState::triggerLoop)
				.def("stopLoop", &LuaGameState::stopLoop)
		];

		for(int i=0;i<luaFile.getNumChildren();++i)
		{
			if(luaFile.getChild(i).getExtension() == L"lua")
			{
				UTF8File::wstringToChar(luaFile.getChild(i).getPath(), luaFilePath);
				luaL_dofile(luaState, luaFilePath.c_str());
			}
		}

		luabind::call_function<void>(luaState, "initialise", getDrawingAreaSize());
	}
	catch(luabind::error& e)
	{
		handleError(lua_tostring(luaState, -1));
	}
	catch(const std::exception &err)
	{
		handleError(err.what());
	}
}

//------------------------------------------------------------------------------
LuaGameState::~LuaGameState()
{
	if(!luaError)
		lua_close(luaState);

	sounder->stopAll();
}

//------------------------------------------------------------------------------
void LuaGameState::update()
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState, "update", this);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}

	if(!fadeTween.getFinished())
	{
		fadeTween.tick();
		if(fade >= 1.0f)
			quit();
	}
}

//------------------------------------------------------------------------------
void LuaGameState::draw(shared_ptr<Drawer>& d, float timestepInterp)
{
	if(!luaDrawer.getDrawer())
		luaDrawer.setDrawer(d);

	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState, "draw", luaDrawer);

			if(!LuaClasses::getDebugOutput().empty())
			{
				luaDrawer.drawText(LuaClasses::getDebugOutput(),
								   "DebugFont",
								   TwoFloats(32.0f, d->getDrawingAreaSize().y*0.5f),
								   d->getDrawingAreaSize().x-64.0f,
								   0,
								   1.0f,
								   ThreeFloats(1.0f, 1.0f, 1.0f),
								   1.0f);
			}
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
	else
	{
		luaDrawer.drawText(lastError,
						   "DebugFont",
						   32.0f,
						   d->getDrawingAreaSize().x-64.0f,
						   0,
						   1.0f,
						   1.0f,
						   1.0f);
	}

	if(fade > 0.0f)
		d->fillRect(0.0f, d->getDrawingAreaSize(), 0.0f, fade);
}

//------------------------------------------------------------------------------
void LuaGameState::keyDown(const std::wstring& action,
						   const std::wstring& keyPressed,
						   int keyModifier)
{
	if(action == L"Escape")
	{
		fadeTween.reset(1.0f);
		sounder->stopAll();
	}
	else if(keyPressed == L"F5")
	{
		LuaClasses::getDebugOutput().clear();
		nextState(L"LuaGameState");
	}
	else if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "keyDown",
										 getNarrowAction(action),
										 getNarrowAction(keyPressed),
										 keyModifier);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::keyUp(const std::wstring& action,
					     const std::wstring& keyPressed,
					     int keyModifier)
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "keyUp",
										 getNarrowAction(action),
										 getNarrowAction(keyPressed),
										 keyModifier);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::mouseDown(int button, const TwoFloats& pos)
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "mouseDown",
										 button,
										 pos);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::mouseUp(int button, const TwoFloats& pos)
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "mouseUp",
										 button,
										 pos);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::mouseMove(const TwoFloats& pos)
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "mouseMove",
										 pos);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::mouseWheel(bool dir)
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "mouseWheel",
										 dir);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::joyMove(const std::wstring& action,
						   float value,
						   int joystick,
						   int axis)
{
	if(!luaError)
	{
		try
		{
			luabind::call_function<void>(luaState,
										 "joyMove",
										 getNarrowAction(action),
										 value,
										 joystick,
										 axis);
		}
		catch(luabind::error& e)
		{
			handleError(lua_tostring(luaState, -1));
		}
		catch(const std::exception &err)
		{
			handleError(err.what());
		}
	}
}

//------------------------------------------------------------------------------
void LuaGameState::playMusic(const std::string& sound)
{
	std::wstring wideSound;

	UTF8File::charToWstring(sound, wideSound);

	sounder->playMusic(wideSound);
}

//------------------------------------------------------------------------------
void LuaGameState::stopMusic()
{
	sounder->stopMusic();
}

//------------------------------------------------------------------------------
void LuaGameState::triggerSound(const std::string& sound,
								float level,
								float pan)
{
	std::wstring wideSound;

	UTF8File::charToWstring(sound, wideSound);

	sounder->triggerSound(wideSound, level, pan);
}

//------------------------------------------------------------------------------
int LuaGameState::triggerLoop(const std::string& sound,
							  float level,
							  float pan,
							  bool fade)
{
	std::wstring wideSound;

	UTF8File::charToWstring(sound, wideSound);

	return sounder->triggerLoop(wideSound, level, pan, fade);
}

//------------------------------------------------------------------------------
void LuaGameState::stopLoop(int index)
{
	sounder->stopLoop(index);
}

//------------------------------------------------------------------------------
void LuaGameState::handleError(const std::string& err)
{
	lastError = err;
	logFile.write(lastError);
	luaError = true;

	if(luaState)
		lua_close(luaState);
}
