// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   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 2 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, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include <iostream>
#include <sstream>
#include <string>
#include <map>
using namespace std;

#include "../console/console.h"
using namespace Console;

#include "../3rdparty/tinyxml/tinyxml.h"

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

#include "luatableserializer.h"

/** @brief Encode a binary string into hex.
	@param str The string to convert.
	@return The encoded string.
*/
std::string hexEncode(const std::string &str) {
	string encoded(str.size() * 2, '?');
	
	const char hexChars[] = "0123456789ABCDEF";
	
	for(unsigned int i = 0; i < str.size(); i++) {
		encoded[i*2]     = hexChars[(str[i] & 0xF0) >> 4];
		encoded[i*2 + 1] = hexChars[str[i] & 0x0F];
	}
	
	return encoded;
}

/** @brief Decode a hex string into a binary string.
	@param str The encoded string.
	@return The decoded string.
*/
std::string hexDecode(const std::string &str) {
	string decoded(str.size() / 2, '?');
	
	// TODO: Error checking?
	
	// This seems a little hackish, but it'll
	// work so long as the incoming string really
	// is a hex string. (THAT USES UPPERCASE!)
	const char decode[] = {
		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
		0, 0, 0, 0, 0, 0, 0,
		10, 11, 12, 13, 14, 15,
	};
	
	for(unsigned int i = 0; i < decoded.size(); i++) {
		decoded[i] = decode[str[i*2 + 1] - '0'] + decode[str[i*2] - '0'] * 16;
	}
	
	return decoded;
}

/** @brief Lua chunk writer for lua_dump that dumps to a hex-encoded string.
	@note You MUST clear the resulting string after lua_dump.
	@param L lua_State pointer.
	@param p Binary data to write.
	@param sz Size of the binary data to write.
	@param ud Pointer to a pointer to store the pointer to the address of the output data.
	@return 0 indicating successful write (to the string in memory).
*/
int hexDumper(lua_State *L, const void *p, size_t sz, void *ud) {
	// Local output string storage.
	static string dumpedHex("");

	// Set the pointer pointed to by the double-pointer to point to the string.
	//string** returnPointer = (string**)ud;
	//*returnPointer = &dumpedHex;
	*((string**)ud) = &dumpedHex;
	
	// Shove all the data into a string.
	string stuffToEncode(sz, 0);
	for(unsigned int i = 0; i < sz; i++) {
		stuffToEncode[i] = ((char*)p)[i];
	}
	
	// Convert the string.
	string newCode = hexEncode(stuffToEncode);

	// Add it to the dumped code.
	dumpedHex = dumpedHex + newCode;
	
	return 0;
}

/** @brief Chunk reader for lua_load that loads from a hex-encoded string.
	@param L lua_State pointer.
	@param data Pointer to an std::string that the incoming hex data is in.
	@param size Pointer to a size_t to store the size of the resulting data in.
	@return A pointer to the loaded, converted data.
*/
const char *hexReader(lua_State *L, void *data, size_t *size) {
	const string *newData = (const string*)data;
	
	// If Lua tries to read more, we have to let it know we're done.
	// FIXME: Nevermind, I'm doing something horribly wrong here.
	//static const string *lastData = NULL;
	//if(newData == lastData) { *size = 0; return NULL; }
	//lastData = newData;
	
	// Decode the data to a binary string.
	static string decodedFunc;
	decodedFunc = hexDecode(*newData);
	
	// Set the size.
	*size = decodedFunc.size();
	
	// Return a pointer to the converted data.
	return decodedFunc.data();
	
}

void LuaTableSerializer::tableFromXml(lua_State *L, TiXmlElement *luaStateXml) {
	if(!luaStateXml) {
		lua_pushnil(L);
		return;
	}
	
	// First, we need to make the reference table.
	lua_newtable(L);
	refTableStackPos = lua_gettop(L);
	
	// Now we need to go through and make all the tables, but not fill them yet!
	for(TiXmlElement *tableXml = luaStateXml->FirstChildElement("table"); tableXml; tableXml = tableXml->NextSiblingElement("table")) {
		lua_newtable(L);
		luaL_ref(L, refTableStackPos);
	}
	
	// Okay, NOW we fill them.
	int refNum = 1;
	for(TiXmlElement *tableXml = luaStateXml->FirstChildElement("table"); tableXml; tableXml = tableXml->NextSiblingElement("table")) {
		// First, get the table from the list.
		lua_rawgeti(L, refTableStackPos, refNum);
		
		// Check for a meta table.
		if(TiXmlElement *metaTableXml = tableXml->FirstChildElement("metatable")) {
			int refNum = 0;
			metaTableXml->QueryIntAttribute("ref", &refNum);
			lua_rawgeti(L, refTableStackPos, refNum);
			lua_setmetatable(L, -2);
		}
		
		// Iterate through the key/value pairs in the XML source.
		for(TiXmlElement *pairXml = tableXml->FirstChildElement("pair"); pairXml; pairXml = pairXml->NextSiblingElement("pair")) {
			TiXmlElement *keyXml = pairXml->FirstChildElement("key");
			TiXmlElement *valXml = pairXml->FirstChildElement("value");
			pushVal(L, keyXml);
			pushVal(L, valXml);
			
			lua_rawset(L, -3);
		}
		
		refNum++;
		
		// We're done with this table. Remove it from the stack.
		lua_pop(L, 1);
	}
	
	// Get the root table back up on top.
	lua_rawgeti(L, refTableStackPos, 1);
	
	// Bring the reference table on top.
	lua_insert(L, -2);
	
	// Remove the reference table from the stack.
	lua_pop(L, 1);
	
	// The root table should be on the top of the stack now.
	
}

string findGlobalNameForMetaTable(lua_State *L) {
	
	if(!lua_getmetatable(L, -1)) {
		// No metatable.
		return "";
	}
	
	lua_getfield(L, -1, "__index");
	
	const void *p1 = lua_topointer(L, -1);
	
	lua_getfield(L, LUA_GLOBALSINDEX, "_G");
	lua_pushnil(L);
	
	string ret = "";
	
	// Iterate through the globals.
	while(lua_next(L, -2)) {
		
		const void *p2 = lua_topointer(L, -1);
		
		if(p1 == p2) {
			
			if(lua_type(L, -2) == LUA_TSTRING) {
			
				ret = string(lua_tostring(L, -2));
			
				lua_pop(L, 2); // Pop value and key
				break;
			}
		}
		
		// Pop the value
		lua_pop(L, 1);
	}
	
	// Pop the globals table
	lua_pop(L, 1);
	
	lua_pop(L, 2); // Pop the __index table and the metatable
	
	return ret;
	
}
	
TiXmlElement *LuaTableSerializer::tableToXml(lua_State *L) {
	
	// Create the XML tree.
	TiXmlElement *xml = new TiXmlElement("luaState");
	
	// Stage 1: Go through all the tables and collect references.
	
	referenceIndices.clear();
	
	lua_newtable(L); // Insert a new table.
	lua_insert(L, -2); // Swap the top two tables (so the table we're saving is back on top).
	refTableStackPos = lua_gettop(L) - 1; // Save the position of the reference table in the stack.

	checkStackTop(L);
	
	// Stage 2: Iterate through those stored table references and save
	//   the data + references in them.
	
	for(unsigned int i = 1; i <= referenceIndices.size(); i++) {
		
		// Create the XML node for this table.
		TiXmlElement *currentTableXml = new TiXmlElement("table");
		currentTableXml->SetAttribute("ref", i);
		xml->LinkEndChild(currentTableXml);
		
		// Get the table onto the top of the stack.
		lua_rawgeti(L, refTableStackPos, i);
		
		// Remember where the current table is in the stack.
		int t = lua_gettop(L);
		
		// Check for a metatable.
		if(lua_getmetatable(L, -1)) {
			
			TiXmlElement *metaTableXml = new TiXmlElement("metatable");
			currentTableXml->LinkEndChild(metaTableXml);
			metaTableXml->SetAttribute("ref", referenceIndices[lua_topointer(L, -1)]);
			
			lua_pop(L, 1);
		}
		
		// Push a nil value so we just get the first key/value pair.
		lua_pushnil(L);

		while(lua_next(L, t)) {
			
			TiXmlElement *currentPairXml = new TiXmlElement("pair");
			currentTableXml->LinkEndChild(currentPairXml);
			
			for(int j = -1; j >= -2; j--) {
				TiXmlElement *kvXml = new TiXmlElement("key");
				currentPairXml->LinkEndChild(kvXml);
				
				if(j == -1) {
					kvXml->SetValue("value");
				} 
				
				switch(lua_type(L, j)) {
					
					case LUA_TTABLE:
						// Tables - Get the reference number and store that.
						kvXml->SetAttribute("type", "tableref");
						kvXml->SetAttribute("value", referenceIndices[lua_topointer(L, j)]);
						break;
					
					case LUA_TSTRING:
						// Strings - Just store the string.
						kvXml->SetAttribute("type", "string");
						kvXml->SetAttribute("value", lua_tostring(L, j));
						break;
					
					case LUA_TNUMBER:
						// Numbers - Same, just store it.
						kvXml->SetAttribute("type", "number");
						kvXml->SetDoubleAttribute("value", lua_tonumber(L, j));
						break;
					
					case LUA_TBOOLEAN:
						// Booleans - Stored as a 1 or 0.
						kvXml->SetAttribute("type", "boolean");
						kvXml->SetAttribute("value", lua_toboolean(L, j));
						break;
					
					case LUA_TNIL:
						// Nil - Doesn't even really need a value attribute.
						kvXml->SetAttribute("type", "nil");
						kvXml->SetAttribute("value", "");
						break;
					
					case LUA_TFUNCTION:
						// Okay. Functions get a little weird. We have
						// to dump them to hex code (base64 would be
						// more efficient, but we'll go for simplicity
						// over space) so that we can put it in the XML
						// document.
						kvXml->SetAttribute("type", "function");
					
						// This pointer will be set to point at the
						// static data inside the hexDumper() function
						// so we can retrieve the converted data.
						string *dumpedHex;
					
						lua_insert(L, j); // Make sure it's on the top of the stack.
						lua_dump(L, hexDumper, (void*)&dumpedHex); // Note: double-pointer for dumpedHex.
						lua_insert(L, j); // Put it back where ever it really belongs.
					
						// Finally, set the data.
						kvXml->SetAttribute("value", (*dumpedHex).c_str());
						
						// Clean up the static data in hexDumper().
						*dumpedHex = "";
						break;
						
					default:
						// TODO: We should probably bomb out with an
						// error here, throw an exception, or whatever.
						//quitWithError(string("Something tried to save a ") + string(lua_typename(L, lua_type(L, j))) + string(" which the Lua table serializer can't handle."));
						//cout << "UNSAVABLE DATA TYPE (" << lua_typename(L, lua_type(L, j)) << ")! SCRIPT WRITER IS OFFICIALLY AN IDIOT." << endl;
						
						out("error") << "Something tried to save a " << lua_typename(L, lua_type(L, j)) << " which the Lua table serializer can't handle." << endl;
						break;
				}
			}
			
			// Pop the value off.
			lua_pop(L, 1);
			
		}
		
		// Pop off this table.
		lua_pop(L, 1);
	}
	
	lua_insert(L, -2); // Put the reference table back on top.
	lua_pop(L, 1); // Pop it off.
	
	return xml;
}

void LuaTableSerializer::pushVal(lua_State *L, TiXmlElement *e) {
	
	if(string(e->Attribute("type")) == "string") {
		
		// Handle string data.
		lua_pushstring(L, e->Attribute("value"));
		
	} else if(string(e->Attribute("type")) == "number") {
		
		// Handle double data.
		double num = 0;
		e->QueryDoubleAttribute("value", &num);
		lua_pushnumber(L, num);
		
	} else if(string(e->Attribute("type")) == "boolean") {
		
		// Handle booleans.
		int num = 0;
		e->QueryIntAttribute("value", &num);
		lua_pushboolean(L, num);
		
	} else if(string(e->Attribute("type")) == "nil") {
		
		// Handle nil values.
		lua_pushnil(L);
		
	} else if(string(e->Attribute("type")) == "tableref") {
		
		// Handle table references.
		int refNum = 0;
		e->QueryIntAttribute("value", &refNum);
		lua_rawgeti(L, refTableStackPos, refNum);
		
	} else if(string(e->Attribute("type")) == "function") {
		
		// TODO: Check errors here.
		// FIXME: OH GOD HORRIBLE HACK TO REMOVE constNESS.
		//lua_load(L, hexReader, (void*)(unsigned int)e->Attribute("value"), "xmlChunk");
		
		string hexValue(e->Attribute("value"));
		lua_load(L, hexReader, &hexValue, "xmlChunk");
		
	} else {
		// Okay. I don't know what the heck this was. But we have to push SOMETHING.
		lua_pushnil(L);
	}
}
	
void LuaTableSerializer::checkStackTop(lua_State *L) {
	if(lua_type(L, -1) == LUA_TTABLE) {
		// It is a table.
		
		// Have we saved a reference for it yet?
		if(!referenceIndices[lua_topointer(L, -1)]) {
			// Nope.
			
			// Get a reference to it.
			int ref = luaL_ref(L, refTableStackPos);
			
			// Push it back onto the stack (luaL_ref() pops it).
			lua_rawgeti(L, refTableStackPos, ref);
			
			// Save the reference to this table.
			referenceIndices[lua_topointer(L, -1)] = ref;
			
			// Recurse.
			genReferences(L);
		}
	}				
}
	
void LuaTableSerializer::genReferences(lua_State *L) {
	
	// TODO: Get the meta table!
	
	// Remember where the current table is in the stack.
	int t = lua_gettop(L);
	
	// Push a nil value so we just get the first key/value pair.
	lua_pushnil(L);

	while(lua_next(L, t)) {
		// Check the value.
		checkStackTop(L);
		
		// At this point the value and key are both on the top of the
		// stack. lua_next uses the key to determine which thing comes
		// next. That needs to be on the top of the stack and the value
		// needs to go away. So we pop it.
		lua_pop(L, 1);
		
		// Oh, and we need to check the key to see if it's a table too.
		checkStackTop(L);
	}
	
	// TODO: Check the metatable too.
	//   May be as simple as getting the metatable and checkStackTop()ing, then popping.
	if(lua_getmetatable(L, -1)) {
		checkStackTop(L);
		lua_pop(L, 1);
	}
}
	

