#include <mof/script/lua_state.hpp>
#include <mof/script/CommandSet.hpp>
#include <mof/ConsoleIO.hpp>
#include <mof/streams.hpp>
#include <mof/utilities.hpp>
#include <mof/script/seq_parser.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/function.hpp>

namespace mof
{
namespace script
{
	lua_state lua_state::singleton_lua;
//{{{ unpack_style
	GameData::entry_t unpack_style(const mof::tstring& style)
	{
		using namespace boost::algorithm;
		using namespace std;

		GameData::entry_t entry;
		vector<mof::tstring> style_items;
		split(style_items, style, is_any_of(";"));
		vector<mof::tstring> key_value;
		foreach (mof::tstring& item, style_items) {
			key_value.clear();
			split(key_value, item, is_any_of("="));
			if (key_value.size() != 2) continue;
			entry[key_value[0]] = key_value[1];
		}
		return entry;
	}
//}}}
//{{{ binders *
	int message_create(const tstring& title, const tstring& style, int priority)
	{ 
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->message_create(thread_id, title, unpack_style(style), priority);
	}
	int message_next(int id, const tstring& text){ return lua_state::instance().command_set()->message_next(id, text); }
	void wait_for_key(mof::InputReceiver::Key key)
	{ 
		auto& l = lua_state::instance();
		l.command_set()->wait_for_key(l.get_target_thread_id(), key);
	}
	void wait_frame(size_t frame)
	{
		auto& l = lua_state::instance();
		l.command_set()->wait_frame(l.get_target_thread_id(), frame);
	}
	int get_last_key(){ return lua_state::instance().command_set()->get_last_key(); }
	//{{{ menu_create
	int menu_create(lua_State* l)
	{
        int n = lua_gettop(l);
        if(!lua_isstring(l , 1))throw std::runtime_error("invalid call");
        if(!lua_istable(l , 2))throw std::runtime_error("invalid call");
        if(!lua_isstring(l , 3))throw std::runtime_error("invalid call");
        if(!lua_isnumber(l , 4))throw std::runtime_error("invalid call");
        mof::tstring title = lua_tostring(l , 1);
        mof::tstring style = lua_tostring(l , 3);
        int priority = lua_tonumber(l , 4);
    
        std::vector<mof::tstring> items;
        for (int i = 1 ; ; i++) {
            lua_pushnumber(l , i);
            lua_gettable(l , 2);
            if (lua_isnil(l , -1)) break;
            if (!lua_isstring(l , -1)) throw std::runtime_error("invalid call");
            items.push_back(lua_tostring(l , -1));
            lua_pop(l , 1);
        }
        lua_pop(l , n);
		if (items.empty()) items.push_back("no items");

		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		int id = lua_state::instance().command_set()->menu_create(thread_id, title, items, unpack_style(style), priority);
		lua_pushnumber(l, id);
        return 1;
    } 
	//}}}
	int menu_move_cursor(int id, CommandSet::MoveDirection direction){ return lua_state::instance().command_set()->menu_move_cursor(id, direction); }
	int menu_get_current(int id){ return lua_state::instance().command_set()->menu_get_current(id); }
	int menu_select(int id){ return lua_state::instance().command_set()->menu_select(id); }
	int sound_create(const mof::tstring& filepath){ return lua_state::instance().command_set()->sound_create(mof::tstring(filepath)); }
	void sound_play(int id){ return lua_state::instance().command_set()->sound_play(id); }
	int music_create(const mof::tstring& filepath){ return lua_state::instance().command_set()->music_create(mof::tstring(filepath)); }
	void music_play(int id){ return lua_state::instance().command_set()->music_play(id); }
	void music_pause(int id){ return lua_state::instance().command_set()->music_pause(id); }
	//{{{ load_game_data
	int load_game_data(lua_State* l)
	{
        int n = lua_gettop(l);
        if(!lua_isstring(l , 1))throw std::runtime_error("invalid call");
		GameData::ptr p = lua_state::instance().command_set()->load_game_data(lua_tostring(l, 1));
		lua_pop(l, n);
       
   
	    lua_newtable(l);
		int tab_index1 = lua_gettop(l);
        std::vector<mof::tstring> items;
		for (int i = 0; i < p->data_.size(); ++i) {
            lua_pushnumber(l, i+1);// lua配列の添え字は1から
	    	lua_newtable(l);
			int tab_index2 = lua_gettop(l);
			for (auto itr = p->data_[i].begin(); itr != p->data_[i].end(); ++itr) {
            	lua_pushstring(l, itr->first.c_str());
            	lua_pushstring(l, itr->second.c_str());
            	lua_settable(l , tab_index2);
			}
            lua_settable(l , tab_index1);
        }
        return 1;

	}
	//}}}
	int picture_create(const mof::tstring& filepath, int priority)
	{ 
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->picture_create(thread_id, filepath, priority);
	}
	int camera_create(){ return lua_state::instance().command_set()->camera_create(); }
	void glue_print_debug(const mof::tstring& text){ return lua_state::instance().command_set()->print_debug(text); }
	int particlegen_create()
	{ 
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->particlegen_create(thread_id);
	}
	int solid_create(const mof::tstring& path, int priority)
	{ 
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->solid_create(thread_id, path, priority);
	}
	//{{{ object_set_behavior
	int object_set_behavior(lua_State* l)
	{
        int n = lua_gettop(l);
        if (!lua_isnumber(l , 1)) throw std::runtime_error("invalid call");
        if (!lua_isstring(l , 2)) throw std::runtime_error("invalid call");
        if (!lua_istable(l , 3))  throw std::runtime_error("invalid call");
        if (!lua_isnumber(l , 4)) throw std::runtime_error("invalid call");
        int id = lua_tonumber(l , 1);
		mof::tstring target = lua_tostring(l , 2);
		int period = lua_tonumber(l, 4);
		
		using namespace boost::algorithm;
		std::vector<mof::tstring> splited_list;
		split(splited_list, target, is_any_of("."));
		if (splited_list.back() == "color") {
			seq_parser<Color4f> parser;
			lua_state::instance().command_set()->set_color_behavior(id, target, parser.parse(l, 3), period);
		} 
		else if (splited_list.back() == "position2") {
			seq_parser<Vector2D> parser;
			lua_state::instance().command_set()->set_position_behavior(id, target, parser.parse(l, 3), period);
		} 
		else if (splited_list.back() == "position3") {
			seq_parser<Vector3D> parser;
			lua_state::instance().command_set()->set_position_behavior(id, target, parser.parse(l, 3));
		} 
		else if (splited_list.back() == "eye") {
			seq_parser<Vector3D> parser;
			lua_state::instance().command_set()->set_camera_behavior(id, target, parser.parse(l, 3));
		}
		else if (splited_list.back() == "lookat") {
			seq_parser<Vector3D> parser;
			lua_state::instance().command_set()->set_camera_behavior(id, target, parser.parse(l, 3));
		}
		else if (splited_list.back() == "size2") {
			seq_parser<Vector2D> parser;
			lua_state::instance().command_set()->set_size_behavior(id, target, parser.parse(l, 3), period);
		}
		else if (splited_list.back() == "size3") {
			seq_parser<Vector3D> parser;
			lua_state::instance().command_set()->set_size_behavior(id, target, parser.parse(l, 3));
		}
		else if (splited_list.back() == "rotate3") {
			seq_parser<Vector3D> parser;
			lua_state::instance().command_set()->set_rotate_behavior(id, target, parser.parse(l, 3));
		}
		else if (splited_list.back() == "texcoord") {
			seq_parser<Rectangle<float>> parser;
			lua_state::instance().command_set()->set_texture_coord_tween(id, target, parser.parse(l, 3));
		}
		else 
		{
			throw std::logic_error("unknown target:" + target);
		}

		lua_settop(l, 0);
        return 0;
	}
	//}}}
	//{{{ glue_save_game_data
	int glue_save_game_data(lua_State* l)
	{
		int n = lua_gettop(l);
        if (!lua_isstring(l, 1)) throw std::runtime_error("invalid call");
        if (!lua_istable(l, 2)) throw std::runtime_error("invalid call");
		mof::tstring resource_path = lua_tostring(l, 1);
		auto p = std::make_shared<GameData>();
	
		lua_pushnil(l);
		for (int index = 0; lua_next(l, 2) != 0; ++index) {
			lua_pushnil(l);
			GameData::entry_t entry;
			while (lua_next(l, -2) != 0) {
				mof::tstring key = lua_tostring(l, -2);
				mof::tstring value = lua_tostring(l, -1);
				entry[key] = value;
				lua_pop(l, 1);
			}
			lua_pop(l, 1);
			p->data_.push_back(entry);
		}
		lua_pop(l, n);

		lua_state::instance().command_set()->save_game_data(resource_path, p);
        return 0;
	}
	//}}}
	//{{{ invoke
	int glue_invoke(lua_State* l)
	{
        int n = lua_gettop(l);
        if (!lua_isthread(l , 1)) throw std::runtime_error("invalid call");
        lua_State* th = lua_tothread(l , 1);
        lua_pop(l, n);
		size_t th_id = lua_state::instance().invoke(th);
		lua_pushnumber(l, th_id);
        return 1;
	}
	//}}}
	//{{{ stop
	int glue_routine_stop(lua_State* l)
	{
        int n = lua_gettop(l);
        if (!lua_isnumber(l , 1)) throw std::runtime_error("invalid call");
        int thread_id = lua_tonumber(l , 1);
        lua_pop(l, n);
		lua_state::instance().stop(thread_id);
		return 0;
	}
	//}}}
	int show(int id, const tstring& class_path){ return lua_state::instance().command_set()->show(id, class_path); }
	int hide(int id, const tstring& class_path){ return lua_state::instance().command_set()->hide(id, class_path); }
	void dispose(int id, const tstring& class_path){ lua_state::instance().command_set()->dispose(id, class_path); }
	void get_property(int id, const tstring& class_path){ lua_state::instance().command_set()->dispose(id, class_path); }
	int glue_put_element(mof::script::CommandSet::ElementType type, int x, int y) {return lua_state::instance().command_set()->put_element(type, x, y);}
	void glue_world_sleep() {lua_state::instance().command_set()->world_sleep();}
	void glue_set_enable(int id, bool is_enable, const tstring& class_path) {lua_state::instance().command_set()->set_enable(id, is_enable, class_path);}
	int glue_board_create(const mof::tstring& filepath, int priority, const mof::tstring& style)
	{ 
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->board_create(thread_id, filepath, unpack_style(style), priority);
	}
	int glue_land_region_create(int x, int y, int priority)
	{ 
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->land_region_create(thread_id, x, y, priority);
	}
	int glue_light_create(const tstring& class_path){
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return lua_state::instance().command_set()->light_create(thread_id, class_path);
	}
	void glue_set_light(int id, int light_id){ lua_state::instance().command_set()->set_light(id, light_id); }
	void glue_solid_joints_solid(int id1, const tstring& joint_type, int id2){ lua_state::instance().command_set()->joint(id1, "solid", joint_type, id2, "solid"); }
	void glue_board_joints_solid(int id1, const tstring& joint_type, int id2){ lua_state::instance().command_set()->joint(id1, "board", joint_type, id2, "solid"); }
	void glue_quit(){ lua_state::instance().command_set()->quit(); }
	//{{{ get_properties
	int get_properties(lua_State* l)
	{
        int n = lua_gettop(l);
        if (!lua_isnumber(l , 1)) throw std::runtime_error("invalid call");
        if(!lua_isstring(l , 2))throw std::runtime_error("invalid call");
        int id = lua_tonumber(l , 1);
		mof::tstring class_path = lua_tostring(l , 2);

		GameData::ptr p = lua_state::instance().command_set()->get_properties(id, class_path);
		lua_pop(l, n);
   
		assert(p->data_.size() == 1);
	    lua_newtable(l);
		int tab_index1 = lua_gettop(l);
        std::vector<mof::tstring> items;

		for (auto itr = p->data_[0].begin(); itr != p->data_[0].end(); ++itr) {
           	lua_pushstring(l, itr->first.c_str());
           	lua_pushstring(l, itr->second.c_str());
           	lua_settable(l , tab_index1);
		}
        return 1;
	}
	//}}}
//}}}
//{{{ Impl
	struct lua_state::Impl
	{
		lua_State* l_;
		std::shared_ptr<mof::script::CommandSet> commands_;
		callback_invoke_t callback_invoke_;
		callback_stop_t callback_stop_;
		size_t thread_id_;

		Impl()
		: l_(NULL), thread_id_(0)
		{
		}

		~Impl()
		{
			if(l_)lua_close(l_);
		}
	};
//}}}
//{{{ constructor
	lua_state::lua_state()
	: impl_(new Impl())
	{	
		impl_->l_ = lua_open();
		luaL_openlibs(impl_->l_);
		luabind::open(impl_->l_);
	}
//}}}
//{{{ destructor
	lua_state::~lua_state(){}
//}}} destructor
//{{{ bind *
	void lua_state::bind
	(
		std::shared_ptr<mof::script::CommandSet> commands,
		const callback_invoke_t& callback_invoke,
		const callback_stop_t& callback_stop
	)
	{
		impl_->commands_ = commands;
		impl_->callback_invoke_ = callback_invoke;
		impl_->callback_stop_ = callback_stop;
		luabind::module(impl_->l_)[luabind::def("messageNewImpl", &message_create)];
		luabind::module(impl_->l_)[luabind::def("messageNextImpl", &message_next)];
		luabind::module(impl_->l_)[luabind::def("waitFrameImpl", &wait_frame)];
		luabind::module(impl_->l_)[luabind::def("waitForKeyImpl", &wait_for_key)];
		luabind::module(impl_->l_)[luabind::def("getLastKeyImpl", &get_last_key)];
		lua_register(impl_->l_, "menuNewImpl", &menu_create);
		luabind::module(impl_->l_)[luabind::def("menuMoveCursorImpl", &menu_move_cursor)];
		luabind::module(impl_->l_)[luabind::def("menuGetCurrentImpl", &menu_get_current)];
		luabind::module(impl_->l_)[luabind::def("menuSelectImpl", &menu_select)];
		luabind::module(impl_->l_)[luabind::def("soundCreateImpl", &sound_create)];
		luabind::module(impl_->l_)[luabind::def("mof_sound_play", &sound_play)];
		luabind::module(impl_->l_)[luabind::def("musicCreateImpl", &music_create)];
		luabind::module(impl_->l_)[luabind::def("mof_music_play", &music_play)];
		luabind::module(impl_->l_)[luabind::def("mof_music_pause", &music_pause)];
		lua_register(impl_->l_, "loadGameDataImpl", &load_game_data);
		lua_register(impl_->l_, "mof_save_game_data", &glue_save_game_data);
		luabind::module(impl_->l_)[luabind::def("mof_print_debug", &glue_print_debug)];
		luabind::module(impl_->l_)[luabind::def("pictureCreateImpl", &picture_create)];
		luabind::module(impl_->l_)[luabind::def("mof_camera_create", &camera_create)];
		luabind::module(impl_->l_)[luabind::def("particlegenCreateImpl", &particlegen_create)];
		luabind::module(impl_->l_)[luabind::def("solidCreateImpl", &solid_create)];
		lua_register(impl_->l_, "objectSetBehaviorImpl", &object_set_behavior);
		luabind::module(impl_->l_)[luabind::def("showImpl", &show)];
		luabind::module(impl_->l_)[luabind::def("hideImpl", &hide)];
		luabind::module(impl_->l_)[luabind::def("disposeImpl", &dispose)];
		luabind::module(impl_->l_)[luabind::def("mof_put_element", &glue_put_element)];
		luabind::module(impl_->l_)[luabind::def("mof_world_sleep", &glue_world_sleep)];
		luabind::module(impl_->l_)[luabind::def("mof_land_region_create", &glue_land_region_create)];
		lua_register(impl_->l_, "objectGetPropertiesImpl", &get_properties);
		lua_register(impl_->l_, "mof_invoke", &glue_invoke);
		lua_register(impl_->l_, "mof_routine_stop", &glue_routine_stop);
		luabind::module(impl_->l_)[luabind::def("mof_board_create", &glue_board_create)];
		luabind::module(impl_->l_)[luabind::def("mof_light_create", &glue_light_create)];
		luabind::module(impl_->l_)[luabind::def("mof_set_light", &glue_set_light)];
		luabind::module(impl_->l_)[luabind::def("mof_solid_joints_solid", &glue_solid_joints_solid)];
		luabind::module(impl_->l_)[luabind::def("mof_board_joints_solid", &glue_board_joints_solid)];
		luabind::module(impl_->l_)[luabind::def("mof_quit", &glue_quit)];
		luabind::module(impl_->l_)[luabind::def("mof_obj_enable", &glue_set_enable)];
	}
//}}}
//{{{ raw_lua
	lua_State* lua_state::raw_lua() const
	{
		return impl_->l_;
	}
//}}}
//{{{ invoke
	size_t lua_state::invoke(lua_State* th) 
	{
		unsigned int thread_id = lua_state::instance().get_target_thread_id();
		return impl_->callback_invoke_(th, thread_id);
	}
//}}}
//{{{ stop
	void lua_state::stop(unsigned int thread_id)
	{
		impl_->callback_stop_(thread_id);
	}
//}}}
//{{{ command_set
	std::shared_ptr<mof::script::CommandSet>& const lua_state::command_set() const
	{
		return impl_->commands_;
	}
//}}}
//{{{ set_target_thread_id
	void lua_state::set_target_thread_id(size_t id) 
	{
		impl_->thread_id_ = id;
	}
//}}}
//{{{ get_target_thread_id
	size_t lua_state::get_target_thread_id() const
	{
		return impl_->thread_id_;
	}
//}}}
//{{{ instance
	lua_state& lua_state::instance()
	{
		return singleton_lua;
	}
//}}}

}
}
