#include "lua.hpp"
#include <iostream>
#include <Ogre.h>
#include <OgreSerializer.h>

#if 0

class LuaScriptFile : public Ogre::Resource
{
     Ogre::String mString;

 protected:

     // must implement these from the Ogre::Resource interface
     void loadImpl();
     void unloadImpl()
     {
	/* If you were storing a pointer to an object, then you would check the pointer here,
	and if it is not NULL, you would destruct the object and set its pointer to NULL again.
	*/
	
	mString.clear();
     }
     size_t calculateSize() const
     {
	return mString.length();
     }

 public:

     LuaScriptFile(Ogre::ResourceManager* creator, const Ogre::String &name,
			    Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual,
			    Ogre::ManualResourceLoader *loader) :
     Ogre::Resource(creator, name, handle, group, isManual, loader)
     {
	/* If you were storing a pointer to an object, then you would set that pointer to NULL here.
	*/

	/* For consistency with StringInterface, but we don't add any parameters here
	That's because the Resource implementation of StringInterface is to
	list all the options that need to be set before loading, of which
	we have none as such. Full details can be set through scripts.
	*/
	createParamDictionary("LuaScriptFile");
     }
     virtual ~LuaScriptFile()
     {
	unload();
     }

     void setString(const Ogre::String &str)
     {
	mString = str;
     }
     const Ogre::String &getString() const
     {
	return mString;
     }
};

class LuaScriptFilePtr: public Ogre::SharedPtr<LuaScriptFile> {
};
class LuaScriptFile; // forward declaration
 
class LuaScriptFileSerializer : public Ogre::Serializer
{
public:
     LuaScriptFileSerializer()
     {
     }
     virtual ~LuaScriptFileSerializer()
     {
     }
 
     void exportLuaScriptFile(const LuaScriptFile *pText, const Ogre::String &fileName)
     {
	std::ofstream outFile;
	outFile.open(fileName.c_str(), std::ios::out);
	outFile << pText->getString();
	outFile.close();
     }
     void importLuaScriptFile(Ogre::DataStreamPtr &stream, LuaScriptFile *pDest)
     {
    	pDest->setString(stream->getAsString());
     }
};

void LuaScriptFile::loadImpl()
{
	LuaScriptFileSerializer serializer;
	Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(mName, mGroup, true, this);
	serializer.importLuaScriptFile(stream, this);
}

class LuaScriptManager : public Ogre::ResourceManager, public Ogre::Singleton<LuaScriptManager>
{
protected:

    // must implement this from ResourceManager's interface
    Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle,
        const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader,
        const Ogre::NameValuePairList *createParams)
    {
	    return new LuaScriptFile(this, name, handle, group, isManual, loader);
    }

public:

    LuaScriptManager()
    {
	    mResourceType = "LuaScript";
	    // low, because it will likely reference other resources
	    mLoadOrder = 30.0f;
	    // this is how we register the ResourceManager with OGRE
	    Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this);
    }
    virtual ~LuaScriptManager()
    {
	    // and this is how we unregister it
	    Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType);
    }

    virtual LuaScriptFilePtr load(const Ogre::String &name, const Ogre::String &group)
    {
	    LuaScriptFilePtr textf = getByName(name);
	    if (textf.isNull())
		    textf = create(name, group);
	    textf->load();
	    return textf;
    }

    static LuaScriptManager &getSingleton()
    {
	    assert(ms_Singleton);
	    return *ms_Singleton;
    }
    static LuaScriptManager *getSingletonPtr()
    {
	    return ms_Singleton;
    }
};

template<> LuaScriptManager *Ogre::Singleton<LuaScriptManager>::ms_Singleton = 0;
#endif

class LuaControl {
	lua_State *L;
//	LuaScriptManager manager;
	Ogre::String lua_string;
	Ogre::SceneManager *mSceneMgr;
	std::list<Ogre::SceneNode *> mBuilderParent;

public:
	Ogre::String dlight_name;
	Ogre::Vector3 dlight_direction;
	Ogre::ColourValue diffuse;
	Ogre::ColourValue specular;
	Ogre::String skybox_name;
	Ogre::Real skybox_size;

public:
	LuaControl(): L(luaL_newstate())
	{
		dlight_name = "Sun";
		dlight_direction = Ogre::Vector3(0.55, -0.3, 0.75).normalisedCopy();
		diffuse = Ogre::ColourValue(Ogre::ColourValue::White);
		specular = Ogre::ColourValue(0.4, 0.4, 0.4);
		skybox_name = "Skybox";
		skybox_size = 490;
		luaopen_base(L);
		luaopen_package(L);
	}
	void mlua_get_field(lua_State *L, const char *field_name)
	{
		/* table is -1 and on top of stack */
		lua_pushstring(L, field_name);
		lua_gettable(L, -2);
		/* the value is now on stack */
	}
	template<class T>
	T mlua_get_udata(lua_State *L, int x)
	{
		T ret;
		T *v = static_cast<T *>(lua_touserdata(L, x));
		ret = *v;
		return ret;
	}
	template<class T>
	const T &mlua_get_udata(lua_State *L, int x) const
	{
		T ret;
		T *v = static_cast<T *>(lua_touserdata(L, x));
		ret = *v;
		return ret;
	}
	int mlua_create_tree(lua_State *L)
	{
		Ogre::SceneNode *root = mSceneMgr->getRootSceneNode();
		lua_pushlightuserdata(L, root);
		luaL_getmetatable(L, "TreeMetaTable");
		assert(lua_istable(L, -1));
		lua_setmetatable(L, -2);

		return 1;
	}
	void setup(Ogre::SceneManager *sceneMgr)
	{
		mSceneMgr = sceneMgr;
		luaL_newmetatable(L, "Vector3MetaTable");
		lua_pushstring(L, "__gc");
		lua_pushcfunction(L, [](lua_State *L) -> int {
			static_cast<Ogre::Vector3 *>(lua_touserdata(L, -1))->Ogre::Vector3::~Vector3();
			return 0;	
		});
		lua_settable(L, -3);
		luaL_newmetatable(L, "QuaternionMetaTable");
		lua_pushstring(L, "__gc");
		lua_pushcfunction(L, [](lua_State *L) -> int {
			static_cast<Ogre::Quaternion *>(lua_touserdata(L, -1))->Ogre::Quaternion::~Quaternion();
			return 0;	
		});
		lua_settable(L, -3);
		luaL_newmetatable(L, "ColourValueMetaTable");
		lua_pushstring(L, "__gc");
		lua_pushcfunction(L, [](lua_State *L) -> int {
			static_cast<Ogre::ColourValue *>(lua_touserdata(L, -1))->Ogre::ColourValue::~ColourValue();
			return 0;	
		});
		lua_settable(L, -3);
		luaL_newmetatable(L, "TreeMetaTable");
		lua_pushstring(L, "__gc");
		lua_pushcfunction(L, [](lua_State *L) -> int {
			return 0;	
		});
		lua_settable(L, -3);
		lua_pushstring(L, "__index");
		lua_pushlightuserdata(L, this);
		lua_pushcclosure(L, [](lua_State *L) -> int {
			if (!lua_isuserdata(L, -2) || !lua_isstring(L, -1)) {
				lua_pushliteral(L, "Bad data");
				lua_error(L);
				return 0;	
			}
			LuaControl *_this = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(1)));
			std::cout << "index: " << lua_tostring(L, -1) << "\n";
			if (!strcmp(lua_tostring(L, -1), "node")) {
				lua_pushvalue(L, -2); // parent userdata
				lua_pushlightuserdata(L, _this);
				lua_pushcclosure(L, [](lua_State *L) -> int {
					int n = lua_gettop(L);
					std::cout << "NOOODE!!!\n";
					Ogre::String node_name;
					Ogre::Quaternion node_rotation;
					Ogre::Vector3 node_translation, node_scale(1, 1, 1);
					bool nameless = false, scale = false;
					if (n == 0)
						return 0;
					if (lua_isstring(L, 1)) {
						node_name = lua_tostring(L, 1);
						if (n >= 2)
							node_rotation = *static_cast<Ogre::Quaternion *>(lua_touserdata(L, 2));
						if (n >= 3)
							node_translation = *static_cast<Ogre::Vector3 *>(lua_touserdata(L, 3));
						if (n == 4)
							node_scale = *static_cast<Ogre::Vector3 *>(lua_touserdata(L, 4));
					} else if (lua_isuserdata(L, 1)){
						node_rotation = *static_cast<Ogre::Quaternion *>(lua_touserdata(L, 1));
						if (n >= 2)
							node_translation = *static_cast<Ogre::Vector3 *>(lua_touserdata(L, 2));
						if (n == 3) {
							node_scale = *static_cast<Ogre::Vector3 *>(lua_touserdata(L, 3));
							scale = true;
						}
						nameless = true;
					}
					Ogre::SceneNode *parent = static_cast<Ogre::SceneNode *>(lua_touserdata(L, lua_upvalueindex(1)));
					std::cout << "parent: " << parent << "\n";
					LuaControl *_this_ = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(2)));
					_this_->mBuilderParent.push_back(parent);
					Ogre::SceneNode *new_node;
				        if(nameless)
						new_node = parent->createChildSceneNode();
					else
						new_node = parent->createChildSceneNode(node_name);
					new_node->setOrientation(node_rotation);
					new_node->setPosition(node_translation);
					if (scale)
						new_node->setScale(node_scale);
					std::cout << "new node: " << new_node << "\n";

					std::cout << "name: " << node_name << " ";
					std::cout << "rotation: " << node_rotation << " ";
					std::cout << "translation: " << node_translation << " ";
					std::cout << "scale: " << node_scale << "\n";
					// lua_pushvalue(L, lua_upvalueindex(1);
					lua_pushlightuserdata(L, new_node);
					luaL_getmetatable(L, "TreeMetaTable");
					assert(lua_istable(L, -1));
					lua_setmetatable(L, -2);
					return 1;
				}, 2);
				return 1;
			} else if (!strcmp(lua_tostring(L, -1), "entity")) {
				// leaf node
				lua_pushvalue(L, -2); // parent userdata
				lua_pushlightuserdata(L, _this);
				lua_pushcclosure(L, [](lua_State *L) -> int {
					int n = lua_gettop(L);
					Ogre::SceneNode *parent = static_cast<Ogre::SceneNode *>(lua_touserdata(L, lua_upvalueindex(1)));
					std::cout << "parent: " << parent << "\n";
					LuaControl *_this_ = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(2)));

					if (n == 1) {
						Ogre::String r = lua_tostring(L, -1);
						Ogre::Entity *ent = _this_->mSceneMgr->createEntity(r);
						parent->attachObject(ent);
						std::cout << "ENTITY: " << r << "\n";
					} else if (n == 2) {
						Ogre::String r = lua_tostring(L, -2);
						Ogre::String p = lua_tostring(L, -1);
						Ogre::Entity *ent = _this_->mSceneMgr->createEntity(r, p);
						parent->attachObject(ent);
						std::cout << "ENTITY: " << r << " " << p << "\n";
					}
					lua_pushlightuserdata(L, parent);
					luaL_getmetatable(L, "TreeMetaTable");
					assert(lua_istable(L, -1));
					lua_setmetatable(L, -2);
					return 1;
				}, 2);
				return 1;
			} else if (!strcmp(lua_tostring(L, -1), "endnode")) {
				lua_pushvalue(L, -2);
				lua_pushlightuserdata(L, _this);
				lua_pushcclosure(L, [](lua_State *L) -> int {
					LuaControl *_this_ = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(2)));
					if (!_this_->mBuilderParent.empty()) {
						Ogre::SceneNode *parent = _this_->mBuilderParent.back();
						lua_pushlightuserdata(L, parent);
						_this_->mBuilderParent.pop_back();
						luaL_getmetatable(L, "TreeMetaTable");
						assert(lua_istable(L, -1));
						lua_setmetatable(L, -2);
					} else {
						lua_pushliteral(L, "No parent for node");
						lua_error(L);
						return 0;
					}
					return 1;
				}, 2);
				return 1;
			}
			return 0;	
		}, 1);
		lua_settable(L, -3);
		lua_pushcfunction(L, [](lua_State *L) -> int {
			int n = lua_gettop(L);
			std::cout << n << "\n";
			if (n == 3) {
				float x = lua_tonumber(L, -3);
				float y = lua_tonumber(L, -2);
				float z = lua_tonumber(L, -1);
				Ogre::Vector3 *v = new(lua_newuserdata(L, sizeof(Ogre::Vector3))) Ogre::Vector3(x, y, z);
				std::cout << *v << "\n";
			} else if (n == 0) {
				Ogre::Vector3 *v = new(lua_newuserdata(L, sizeof(Ogre::Vector3))) Ogre::Vector3(0, 0, 0);
				std::cout << *v << "\n";
			} else {
				lua_pushliteral(L, "Bad Vector3");
				lua_error(L);
				return 0;
			}
			luaL_getmetatable(L, "Vector3MetaTable");
			assert(lua_istable(L, -1));
			lua_setmetatable(L, -2);

			return 1;
		});
		lua_setglobal(L, "Vector3");
		lua_pushcfunction(L, [](lua_State *L) -> int {
			int n = lua_gettop(L);
			std::cout << n << "\n";
			if (n == 4) {
				float r = lua_tonumber(L, -4);
				float g = lua_tonumber(L, -3);
				float b = lua_tonumber(L, -2);
				float a = lua_tonumber(L, -1);
				Ogre::ColourValue *v = new(lua_newuserdata(L, sizeof(Ogre::ColourValue))) Ogre::ColourValue(r, g, b, a);
				std::cout << *v << "\n";
			} else if (n == 3) {
				float r = lua_tonumber(L, -3);
				float g = lua_tonumber(L, -2);
				float b = lua_tonumber(L, -1);
				Ogre::ColourValue *v = new(lua_newuserdata(L, sizeof(Ogre::ColourValue))) Ogre::ColourValue(r, g, b);
				std::cout << *v << "\n";
			} else if (n == 0) {
				Ogre::ColourValue *v = new(lua_newuserdata(L, sizeof(Ogre::ColourValue))) Ogre::ColourValue();
				std::cout << *v << "\n";
			} else {
				lua_pushliteral(L, "Bad ColourValue");
				lua_error(L);
				return 0;
			}
			luaL_getmetatable(L, "ColourValueMetaTable");
			assert(lua_istable(L, -1));
			lua_setmetatable(L, -2);

			return 1;
		});
		lua_setglobal(L, "ColourValue");
		lua_pushcfunction(L, [](lua_State *L) -> int {
			int n = lua_gettop(L);
			std::cout << n << "\n";
			if (n == 4) {
				// normal
				float w = lua_tonumber(L, -4);
				float x = lua_tonumber(L, -3);
				float y = lua_tonumber(L, -2);
				float z = lua_tonumber(L, -1);
				Ogre::Quaternion *v = new(lua_newuserdata(L, sizeof(Ogre::Quaternion))) Ogre::Quaternion(w, x, y, z);
				std::cout << *v << "\n";
			} else if (n == 3) {
				// 3 axis rotation (euler)
				// yaw, pitch, roll
				float rx = lua_tonumber(L, -3); // pitch
				float ry = lua_tonumber(L, -2); // yaw
				float rz = lua_tonumber(L, -1); // roll
				Ogre::Quaternion q1(Ogre::Radian(rx), Ogre::Vector3(1, 0, 0));
				Ogre::Quaternion q2(Ogre::Radian(ry), Ogre::Vector3(0, 1, 0));
				Ogre::Quaternion q3(Ogre::Radian(rz), Ogre::Vector3(0, 0, 1));
				/* 2-1-3 sequence */
				Ogre::Quaternion *v = new(lua_newuserdata(L, sizeof(Ogre::Quaternion))) Ogre::Quaternion(q2 * q1 * q3);

				std::cout << *v << "\n";
			} else if (n == 2) {
				// axis rotation 
				float rot = lua_tonumber(L, -2);
				Ogre::Vector3 *axis = static_cast<Ogre::Vector3 *>(lua_touserdata(L, -1));
				if (axis) {
					Ogre::Quaternion *v = new(lua_newuserdata(L, sizeof(Ogre::Quaternion))) Ogre::Quaternion(Ogre::Radian(rot), *axis);
					std::cout << *v << "\n";
				} else {
					lua_pushliteral(L, "Bad quaternion");
					lua_error(L);
					return 0;
				}
			} else if (n == 0) {
				Ogre::Quaternion *v = new(lua_newuserdata(L, sizeof(Ogre::Quaternion))) Ogre::Quaternion();
				std::cout << *v << "\n";
			} else {
				lua_pushliteral(L, "Bad quaternion");
				lua_error(L);
				return 0;
			}
			luaL_getmetatable(L, "Vector3MetaTable");
			assert(lua_istable(L, -1));
			lua_setmetatable(L, -2);
			return 1;
		});
		lua_setglobal(L, "Quaternion");
#if 0
		lua_pushlightuserdata(L, this);
		lua_pushcclosure(L, [](lua_State *L) -> int {
			int n = lua_gettop(L);
			std::cout << n << "\n";
			if (!lua_islightuserdata(L, lua_upvalueindex(1))) {
				lua_pushliteral(L, "No object");
				lua_error(L);
				return 0;
			}
			if (!lua_istable(L, -1)) {
				lua_pushliteral(L, "No table");
				lua_error(L);
				return 0;
			}
			LuaControl *_this = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(1)));
			/* table is on top of stack */
			_this->mlua_get_field(L, "name");
			if (!lua_isstring(L, -1)) {
				lua_pushliteral(L, "Bad object name");
				lua_error(L);
			}
			Ogre::String objname = lua_tostring(L, -1);
			lua_pop(L, 1);
			/* table is on top of stack */
			_this->mlua_get_field(L, "type");
			if (!lua_isstring(L, -1)) {
				lua_pushliteral(L, "Bad object type");
				lua_error(L);
			}
			Ogre::String objtype = lua_tostring(L, -1);
			lua_pop(L, 1);
			/* table is on top of stack */
			_this->add_obj(L, objname, objtype);
			std::cout << "name: " << objname << " type: " << objtype << "\n";
			std::cout << "object ok\n";
			return 0;
		}, 1);
		lua_setglobal(L, "object");
#endif
		lua_pushlightuserdata(L, this);
		lua_pushcclosure(L, [](lua_State *L) -> int {
			LuaControl *_this = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(1)));
			int ret = _this->mlua_create_tree(L);
			return ret;
		}, 1);
		lua_setglobal(L, "tree");
		lua_pushlightuserdata(L, this);
		lua_pushcclosure(L, [](lua_State *L) -> int {
			LuaControl *_this = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(1)));
			int n = lua_gettop(L);
			if (n >= 1)
				_this->dlight_name = lua_tostring(L, 1);
			if (n >= 2)
				_this->dlight_direction = _this->mlua_get_udata<Ogre::Vector3>(L, 2).normalisedCopy();
			if (n >= 3)
				_this->diffuse = _this->mlua_get_udata<Ogre::ColourValue>(L, 3);
			if (n >= 4)
				_this->specular = _this->mlua_get_udata<Ogre::ColourValue>(L, 4);
			return 0;
		}, 1);
		lua_setglobal(L, "sun");
		lua_pushlightuserdata(L, this);
		lua_pushcclosure(L, [](lua_State *L) -> int {
			LuaControl *_this = static_cast<LuaControl *>(lua_touserdata(L, lua_upvalueindex(1)));
			int n = lua_gettop(L);
			if (n >= 1)
				_this->skybox_name = lua_tostring(L, 1);
			if (n >= 2)
				_this->skybox_size = lua_tonumber(L, 1);
			return 0;
		}, 1);
		lua_setglobal(L, "skybox");

//		LuaScriptFile script;
		Ogre::DataStreamList streams = Ogre::ResourceGroupManager::getSingleton().openResources("*.lua", "LuaScripts");
		while (!streams.empty()) {
			Ogre::DataStreamPtr s = streams.front();
			std::cout << "stream: " << s->getAsString() << "\n";
			streams.pop_front();
			if (luaL_dostring(L, s->getAsString().c_str()) != LUA_OK)
				std::cout << "error: " << lua_tostring(L, -1) << "\n";

		}
		const char *lua_code = "\n\
			function stuff()\n\
				return 4\n\
			end\n\
			x = stuff()\n\
		";
		luaL_dostring(L, lua_code);
		lua_getglobal(L, "x");
		int x = lua_tonumber(L, 1);
		std::cout << "lua: " << x << "\n";
	}
	~LuaControl()
	{
		if (L)
			lua_close(L);
		L = nullptr;
	}
};

