/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

/**
 * This code is heavily based on the Pluto code base. Copyright below
 */

/* Tamed Pluto - Heavy-duty persistence for Lua
 * Copyright (C) 2004 by Ben Sunshine-Hill, and released into the public
 * domain. People making use of this software as part of an application
 * are politely requested to email the author at sneftel@gmail.com
 * with a brief description of the application, primarily to satisfy his
 * curiosity.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Instrumented by Stefan Reich (info@luaos.net)
 * for Mobile Lua (http://luaos.net/pages/mobile-lua.php)
 */


#include "lua_persistence.h"

#include "double_serialization.h"
#include "lua_persistence_util.h"

#include "common/stream.h"

#include "lobject.h"
#include "lstate.h"
#include "lgc.h"
#include "lopcodes.h"


namespace Lua {

struct UnSerializationInfo {
	lua_State *luaState;
	Common::ReadStream *readStream;
};

static void unpersist(UnSerializationInfo *info);

static void unpersistBoolean(UnSerializationInfo *info);
static void unpersistNumber(UnSerializationInfo *info);
static void unpersistString(UnSerializationInfo *info);
static void unpersistTable(UnSerializationInfo *info, int index);
static void unpersistFunction(UnSerializationInfo *info, int index);
static void unpersistThread(UnSerializationInfo *info, int index);
static void unpersistProto(UnSerializationInfo *info, int index);
static void unpersistUpValue(UnSerializationInfo *info, int index);
static void unpersistUserData(UnSerializationInfo *info, int index);
static void unpersistPermanent(UnSerializationInfo *info, int index);


void unpersistLua(lua_State *luaState, Common::ReadStream *readStream) {
	UnSerializationInfo info;
	info.luaState = luaState;
	info.readStream = readStream;

	// The process starts with the lua stack as follows:
	// >>>>> permTbl
	// That's the table of permanents

	// Make sure there is enough room on the stack
	lua_checkstack(luaState, 3);

	// Create a table to hold indexes of everything thats already been read
	lua_newtable(luaState);
	// >>>>> permTbl indexTbl

	// Prevent garbage collection while we unserialize
	lua_gc(luaState, LUA_GCSTOP, 0);

	// Unserialize the root object
	unpersist(&info);
	// >>>>> permTbl indexTbl rootObj

	// Re-start garbage collection
	lua_gc(luaState, LUA_GCRESTART, 0);

	// Remove the indexTbl
	lua_replace(luaState, 2);
	// >>>>> permTbl rootObj
}

/* The object is left on the stack. This is primarily used by unserialize, but
 * may be used by GCed objects that may incur cycles in order to preregister
 * the object. */
static void registerObjectInIndexTable(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ...... obj

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 1);

	lua_pushvalue(info->luaState, -1);
	// >>>>> permTbl indexTbl ...... obj obj

	// Push the k/v pair into the indexTbl
	lua_rawseti(info->luaState, 2, index);
	// >>>>> permTbl indexTbl ...... obj
}

static void unpersist(UnSerializationInfo *info) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 2);

	byte isARealValue = info->readStream->readByte();
	if (isARealValue) {
		int index = info->readStream->readSint32LE();
		int type = info->readStream->readSint32LE();

		switch (type) {
		case LUA_TBOOLEAN:
			unpersistBoolean(info);
			break;
		case LUA_TLIGHTUSERDATA:
			// You can't serialize a pointer
			// It would be meaningless on the next run
			assert(0);
			break;
		case LUA_TNUMBER:
			unpersistNumber(info);
			break;
		case LUA_TSTRING:
			unpersistString(info);
			break;
		case LUA_TTABLE:
			unpersistTable(info, index);
			break;
		case LUA_TFUNCTION:
			unpersistFunction(info, index);
			break;
		case LUA_TTHREAD:
			unpersistThread(info, index);
			break;
		case LUA_TPROTO:
			unpersistProto(info, index);
			break;
		case LUA_TUPVAL:
			unpersistUpValue(info, index);
			break;
		case LUA_TUSERDATA:
			unpersistUserData(info, index);
			break;
		case PERMANENT_TYPE:
			unpersistPermanent(info, index);
			break;
		default:
			assert(0);
		}


		// >>>>> permTbl indexTbl ...... obj
		assert(lua_type(info->luaState, -1) == type ||
		       type == PERMANENT_TYPE ||
		       // Remember, upvalues get a special dispensation, as described in boxUpValue
		       (lua_type(info->luaState, -1) == LUA_TFUNCTION && type == LUA_TUPVAL));

		registerObjectInIndexTable(info, index);
		// >>>>> permTbl indexTbl ...... obj
	} else {
		int index = info->readStream->readSint32LE();

		if (index == 0) {
			lua_pushnil(info->luaState);
			// >>>>> permTbl indexTbl ...... nil
		} else {
			// Fetch the object from the indexTbl

			lua_rawgeti(info->luaState, 2, index);
			// >>>>> permTbl indexTbl ...... ?obj?

			assert(!lua_isnil(info->luaState, -1));
		}
		// >>>>> permTbl indexTbl ...... obj/nil
	}

	// >>>>> permTbl indexTbl ...... obj/nil
}

static void unpersistBoolean(UnSerializationInfo *info) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 1);

	int value = info->readStream->readSint32LE();

	lua_pushboolean(info->luaState, value);
	// >>>>> permTbl indexTbl ...... bool
}

static void unpersistNumber(UnSerializationInfo *info) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 1);

	// Read the serialized double
	Util::SerializedDouble serializedValue;
	serializedValue.significandOne = info->readStream->readUint32LE();
	serializedValue.signAndSignificandTwo = info->readStream->readUint32LE();
	serializedValue.exponent = info->readStream->readSint16LE();

	lua_Number value = Util::decodeDouble(serializedValue);

	lua_pushnumber(info->luaState, value);
	// >>>>> permTbl indexTbl ...... num
}

static void unpersistString(UnSerializationInfo *info) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 1);

	uint32 length = info->readStream->readUint32LE();
	char *string = new char[length];

	info->readStream->read(string, length);
	lua_pushlstring(info->luaState, string, length);

	// >>>>> permTbl indexTbl ...... string

	delete[] string;
}

static void unserializeSpecialTable(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 1);

	unpersist(info);

	// >>>>> permTbl indexTbl ...... spfunc
	lua_call(info->luaState, 0, 1);
	// >>>>> permTbl indexTbl ...... tbl
}

static void unserializeLiteralTable(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 3);

	// Preregister table for handling of cycles
	lua_newtable(info->luaState);

	// >>>>> permTbl indexTbl ...... tbl
	registerObjectInIndexTable(info, index);
	// >>>>> permTbl indexTbl ...... tbl

	// Unserialize metatable
	unpersist(info);
	// >>>>> permTbl indexTbl ...... tbl ?metaTbl/nil?

	if (lua_istable(info->luaState, -1)) {
		// >>>>> permTbl indexTbl ...... tbl metaTbl
		lua_setmetatable(info->luaState, -2);
		// >>>>> permTbl indexTbl ...... tbl
	} else {
		// >>>>> permTbl indexTbl ...... tbl nil
		lua_pop(info->luaState, 1);
		// >>>>> permTbl indexTbl ...... tbl
	}
	// >>>>> permTbl indexTbl ...... tbl


	while (1) {
		// >>>>> permTbl indexTbl ...... tbl
		unpersist(info);
		// >>>>> permTbl indexTbl ...... tbl key/nil

		// The table serialization is nil terminated
		if (lua_isnil(info->luaState, -1)) {
			// >>>>> permTbl indexTbl ...... tbl nil
			lua_pop(info->luaState, 1);
			// >>>>> permTbl indexTbl ...... tbl

			break;
		}

		// >>>>> permTbl indexTbl ...... tbl key
		unpersist(info);
		// >>>>> permTbl indexTbl ...... tbl value

		lua_rawset(info->luaState, -3);
		// >>>>> permTbl indexTbl ...... tbl
	}
}

void unpersistTable(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 1);

	int isSpecial = info->readStream->readSint32LE();

	if (isSpecial) {
		unserializeSpecialTable(info, index);
		// >>>>> permTbl indexTbl ...... tbl
	} else {
		unserializeLiteralTable(info, index);
		// >>>>> permTbl indexTbl ...... tbl
	}
}

void unpersistFunction(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 2);

	byte numUpValues = info->readStream->readByte();

	LClosure *lclosure = (LClosure *)lua_newLclosure(info->luaState, numUpValues, hvalue(&info->luaState->l_gt));
	pushClosure(info->luaState, (Closure *)lclosure);
	// >>>>> permTbl indexTbl ...... func

	// Put *some* proto in the closure, before the GC can find it
	lclosure->p = makeFakeProto(info->luaState, numUpValues);

	//Also, we need to temporarily fill the upvalues
	lua_pushnil(info->luaState);
	// >>>>> permTbl indexTbl ...... func nil

	for (byte i = 0; i < numUpValues; ++i) {
		lclosure->upvals[i] = createUpValue(info->luaState, -1);
	}

	lua_pop(info->luaState, 1);
	// >>>>> permTbl indexTbl ...... func

	// I can't see offhand how a function would ever get to be self-
	// referential, but just in case let's register it early
	registerObjectInIndexTable(info, index);

	// Now that it's safe, we can get the real proto
	unpersist(info);
	// >>>>> permTbl indexTbl ...... func proto

	lclosure->p = gco2p(getObject(info->luaState, -1)->value.gc);

	lua_pop(info->luaState, 1);
	// >>>>> permTbl indexTbl ...... func

	for (byte i = 0; i < numUpValues; ++i) {
		// >>>>> permTbl indexTbl ...... func
		unpersist(info);
		// >>>>> permTbl indexTbl ...... func func2

		unboxUpValue(info->luaState);
		// >>>>> permTbl indexTbl ...... func upValue
		lclosure->upvals[i] = gco2uv(getObject(info->luaState, -1)->value.gc);

		lua_pop(info->luaState, 1);
		// >>>>> permTbl indexTbl ...... func
	}

	// Finally, the fenv
	unpersist(info);

	// >>>>> permTbl indexTbl ...... func ?fenv/nil?
	if (!lua_isnil(info->luaState, -1)) {
		// >>>>> permTbl indexTbl ...... func fenv
		lua_setfenv(info->luaState, -2);
		// >>>>> permTbl indexTbl ...... func
	} else {
		// >>>>> permTbl indexTbl ...... func nil
		lua_pop(info->luaState, 1);
		// >>>>> permTbl indexTbl ...... func
	}

	// >>>>> permTbl indexTbl ...... func
}

void unpersistThread(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	lua_State *L2;
	uint32 stacklimit = 0;

	L2 = lua_newthread(info->luaState);
	lua_checkstack(info->luaState, 3);

	// L1: permTbl indexTbl ...... thread
	// L2: (empty)
	registerObjectInIndexTable(info, index);

	// First, deserialize the object stack
	uint32 stackSize = info->readStream->readUint32LE();
	lua_checkstack(info->luaState, (int)stackSize);

	// Make sure that the first stack element (a nil, representing
	// the imaginary top-level C function) is written to the very,
	// very bottom of the stack
	L2->top--;
	for (uint32 i = 0; i < stackSize; ++i) {
		unpersist(info);
		// L1: permTbl indexTbl ...... thread obj*
	}

	lua_xmove(info->luaState, L2, stackSize);
	// L1: permTbl indexTbl ...... thread
	// L2: obj*

	// Hereafter, stacks refer to L1


	// Now, deserialize the CallInfo stack

	uint32 numFrames = info->readStream->readUint32LE();

	lua_reallocCallInfo(L2, numFrames * 2);
	for (uint32 i = 0; i < numFrames; ++i) {
		CallInfo *ci = L2->base_ci + i;
		uint32 stackbase = info->readStream->readUint32LE();
		uint32 stackfunc = info->readStream->readUint32LE();
		uint32 stacktop = info->readStream->readUint32LE();

		ci->nresults = info->readStream->readSint32LE();

		uint32 savedpc = info->readStream->readUint32LE();

		if (stacklimit < stacktop) {
			stacklimit = stacktop;
		}

		ci->base = L2->stack + stackbase;
		ci->func = L2->stack + stackfunc;
		ci->top = L2->stack + stacktop;
		ci->savedpc = (ci != L2->base_ci) ? ci_func(ci)->l.p->code + savedpc : 0;
		ci->tailcalls = 0;

		// Update the pointer each time, to keep the GC happy
		L2->ci = ci;
	}

	// >>>>> permTbl indexTbl ...... thread
	// Deserialize the state's other parameters, with the exception of upval stuff

	L2->savedpc = L2->ci->savedpc;
	L2->status = info->readStream->readByte();
	uint32 stackbase = info->readStream->readUint32LE();
	uint32 stacktop = info->readStream->readUint32LE();


	L2->errfunc = info->readStream->readUint32LE();

	L2->base = L2->stack + stackbase;
	L2->top = L2->stack + stacktop;

	// Finally, "reopen" upvalues. See serializeUpVal() for why we do this
	UpVal *uv;
	GCObject **nextslot = &L2->openupval;
	global_State *g = G(L2);

	while (true) {
		unpersist(info);
		// >>>>> permTbl indexTbl ...... thread upVal/nil

		// The list is terminated by a nil
		if (lua_isnil(info->luaState, -1)) {
			// >>>>> permTbl indexTbl ...... thread nil
			lua_pop(info->luaState, 1);
			// >>>>> permTbl indexTbl ...... thread
			break;
		}

		// >>>>> permTbl indexTbl ...... thread boxedUpVal
		unboxUpValue(info->luaState);
		// >>>>> permTbl indexTbl ...... thread boxedUpVal

		uv = &(getObject(info->luaState, -1)->value.gc->uv);
		lua_pop(info->luaState, 1);
		// >>>>> permTbl indexTbl ...... thread

		uint32 stackpos = info->readStream->readUint32LE();
		uv->v = L2->stack + stackpos;

		GCUnlink(info->luaState, (GCObject *)uv);

		uv->marked = luaC_white(g);
		*nextslot = (GCObject *)uv;
		nextslot = &uv->next;
		uv->u.l.prev = &G(L2)->uvhead;
		uv->u.l.next = G(L2)->uvhead.u.l.next;
		uv->u.l.next->u.l.prev = uv;
		G(L2)->uvhead.u.l.next = uv;
		lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
	}
	*nextslot = NULL;

	// The stack must be valid at least to the highest value among the CallInfos
	// 'top' and the values up to there must be filled with 'nil'
	lua_checkstack(L2, (int)stacklimit);
	for (StkId o = L2->top; o <= L2->top + stacklimit; ++o) {
		setnilvalue(o);
	}
}

void unpersistProto(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// We have to be careful. The GC expects a lot out of protos. In particular, we need
	// to give the function a valid string for its source, and valid code, even before we
	// actually read in the real code.
	TString *source = lua_newlstr(info->luaState, "", 0);
	Proto *p = lua_newproto(info->luaState);
	p->source = source;
	p->sizecode = 1;
	p->code = (Instruction *)lua_reallocv(info->luaState, NULL, 0, 1, sizeof(Instruction));
	p->code[0] = CREATE_ABC(OP_RETURN, 0, 1, 0);
	p->maxstacksize = 2;
	p->sizek = 0;
	p->sizep = 0;

	lua_checkstack(info->luaState, 2);

	pushProto(info->luaState, p);
	// >>>>> permTbl indexTbl ...... proto

	// We don't need to register early, since protos can never ever be
	// involved in cyclic references

	// Read in constant references
	int sizek = info->readStream->readSint32LE();
	lua_reallocvector(info->luaState, p->k, 0, sizek, TValue);
	for (int i = 0; i < sizek; ++i) {
		// >>>>> permTbl indexTbl ...... proto
		unpersist(info);
		// >>>>> permTbl indexTbl ...... proto  k

		setobj2s(info->luaState, &p->k[i], getObject(info->luaState, -1));
		p->sizek++;

		lua_pop(info->luaState, 1);
		// >>>>> permTbl indexTbl ...... proto
	}
	// >>>>> permTbl indexTbl ...... proto

	// Read in sub-proto references

	int sizep = info->readStream->readSint32LE();
	lua_reallocvector(info->luaState, p->p, 0, sizep, Proto *);
	for (int i = 0; i < sizep; ++i) {
		// >>>>> permTbl indexTbl ...... proto
		unpersist(info);
		// >>>>> permTbl indexTbl ...... proto  subproto

		p->p[i] = (Proto *)getObject(info->luaState, -1)->value.gc;
		p->sizep++;

		lua_pop(info->luaState, 1);
		// >>>>> permTbl indexTbl ...... proto
	}
	// >>>>> permTbl indexTbl ...... proto


	// Read in code
	p->sizecode = info->readStream->readSint32LE();
	lua_reallocvector(info->luaState, p->code, 1, p->sizecode, Instruction);
	info->readStream->read(p->code, sizeof(Instruction) * p->sizecode);


	/* Read in upvalue names */
	p->sizeupvalues = info->readStream->readSint32LE();
	if (p->sizeupvalues) {
		lua_reallocvector(info->luaState, p->upvalues, 0, p->sizeupvalues, TString *);
		for (int i = 0; i < p->sizeupvalues; ++i) {
			// >>>>> permTbl indexTbl ...... proto
			unpersist(info);
			// >>>>> permTbl indexTbl ...... proto str

			p->upvalues[i] = lua_newlstr(info->luaState, lua_tostring(info->luaState, -1), strlen(lua_tostring(info->luaState, -1)));
			lua_pop(info->luaState, 1);
			// >>>>> permTbl indexTbl ...... proto
		}
	}
	// >>>>> permTbl indexTbl ...... proto

	// Read in local variable infos
	p->sizelocvars = info->readStream->readSint32LE();
	if (p->sizelocvars) {
		lua_reallocvector(info->luaState, p->locvars, 0, p->sizelocvars, LocVar);
		for (int i = 0; i < p->sizelocvars; ++i) {
			// >>>>> permTbl indexTbl ...... proto
			unpersist(info);
			// >>>>> permTbl indexTbl ...... proto str

			p->locvars[i].varname = lua_newlstr(info->luaState, lua_tostring(info->luaState, -1), strlen(lua_tostring(info->luaState, -1)));
			lua_pop(info->luaState, 1);
			// >>>>> permTbl indexTbl ...... proto

			p->locvars[i].startpc = info->readStream->readSint32LE();
			p->locvars[i].endpc = info->readStream->readSint32LE();
		}
	}
	// >>>>> permTbl indexTbl ...... proto

	// Read in source string
	unpersist(info);
	// >>>>> permTbl indexTbl ...... proto sourceStr

	p->source = lua_newlstr(info->luaState, lua_tostring(info->luaState, -1), strlen(lua_tostring(info->luaState, -1)));
	lua_pop(info->luaState, 1);
	// >>>>> permTbl indexTbl ...... proto

	// Read in line numbers
	p->sizelineinfo = info->readStream->readSint32LE();
	if (p->sizelineinfo) {
		lua_reallocvector(info->luaState, p->lineinfo, 0, p->sizelineinfo, int);
		info->readStream->read(p->lineinfo, sizeof(int) * p->sizelineinfo);
	}


	/* Read in linedefined and lastlinedefined */
	p->linedefined = info->readStream->readSint32LE();
	p->lastlinedefined = info->readStream->readSint32LE();

	// Read in misc values
	p->nups = info->readStream->readByte();
	p->numparams = info->readStream->readByte();
	p->is_vararg = info->readStream->readByte();
	p->maxstacksize = info->readStream->readByte();
}

void unpersistUpValue(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......
	lua_checkstack(info->luaState, 2);

	boxUpValue_start(info->luaState);
	// >>>>> permTbl indexTbl ...... func
	registerObjectInIndexTable(info, index);

	unpersist(info);
	// >>>>> permTbl indexTbl ...... func obj

	boxUpValue_finish(info->luaState);
	// >>>>> permTbl indexTbl ...... func
}

void unpersistUserData(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 2);

	int isspecial = info->readStream->readSint32LE();
	if (isspecial) {
		unpersist(info);
		// >>>>> permTbl indexTbl ...... specialFunc

		lua_call(info->luaState, 0, 1);
		// >>>>> permTbl indexTbl ...... udata
	} else {
		uint32 length = info->readStream->readUint32LE();
		lua_newuserdata(info->luaState, length);
		// >>>>> permTbl indexTbl ...... udata
		registerObjectInIndexTable(info, index);

		info->readStream->read(lua_touserdata(info->luaState, -1), length);

		unpersist(info);
		// >>>>> permTbl indexTbl ...... udata metaTable/nil

		lua_setmetatable(info->luaState, -2);
		// >>>>> permTbl indexTbl ...... udata
	}
	// >>>>> permTbl indexTbl ...... udata
}

void unpersistPermanent(UnSerializationInfo *info, int index) {
	// >>>>> permTbl indexTbl ......

	// Make sure there is enough room on the stack
	lua_checkstack(info->luaState, 2);

	unpersist(info);
	// >>>>> permTbl indexTbl ...... permKey

	lua_gettable(info->luaState, 1);
	// >>>>> permTbl indexTbl ...... perm
}

} // End of namespace Lua
