/* 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 distri8buted 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)
 */

/**
 * 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 "sword25/util/lua_persistence_util.h"

#include "common/scummsys.h"

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


namespace Lua {

void *lua_realloc(lua_State *luaState, void *block, size_t osize, size_t nsize) {
	global_State *globalState = G(luaState);

	block = (*globalState->frealloc)(globalState->ud, block, osize, nsize);
	globalState->totalbytes = (globalState->totalbytes - osize) + nsize;

	return block;
}

void pushObject(lua_State *luaState, TValue *obj) {
	setobj2s(luaState, luaState->top, obj);

	api_check(luaState, luaState->top < luaState->ci->top);
	luaState->top++;
}

void pushProto(lua_State *luaState, Proto *proto) {
	TValue obj;
	setptvalue(luaState, &obj, proto);

	pushObject(luaState, &obj);
}

void pushUpValue(lua_State *luaState, UpVal *upval) {
	TValue obj;

	obj.value.gc = cast(GCObject *, upval);
	obj.tt = LUA_TUPVAL;
	checkliveness(G(L), obj);

	pushObject(luaState, &obj);
}

void pushString(lua_State *luaState, TString *str) {
	TValue o;
	setsvalue(luaState, &o, str);

	pushObject(luaState, &o);
}

/* A simple reimplementation of the unfortunately static function luaA_index.
 * Does not support the global table, registry, or upvalues. */
StkId getObject(lua_State *luaState, int stackpos) {
	if (stackpos > 0) {
		lua_assert(luaState->base + stackpos - 1 < luaState->top);
		return luaState->base + stackpos - 1;
	} else {
		lua_assert(L->top - stackpos >= L->base);
		return luaState->top + stackpos;
	}
}

void lua_linkObjToGC(lua_State *luaState, GCObject *obj, lu_byte type) {
	global_State *globalState = G(luaState);

	obj->gch.next = globalState->rootgc;
	globalState->rootgc = obj;
	obj->gch.marked = luaC_white(globalState);
	obj->gch.tt = type;
}

Closure *lua_newLclosure(lua_State *luaState, int numElements, Table *elementTable) {
	Closure *c = (Closure *)lua_malloc(luaState, sizeLclosure(numElements));
	lua_linkObjToGC(luaState, obj2gco(c), LUA_TFUNCTION);

	c->l.isC = 0;
	c->l.env = elementTable;
	c->l.nupvalues = cast_byte(numElements);

	while (numElements--) {
		c->l.upvals[numElements] = NULL;
	}

	return c;
}

void pushClosure(lua_State *luaState, Closure *closure) {
	TValue obj;
	setclvalue(luaState, &obj, closure);
	pushObject(luaState, &obj);
}

Proto *createProto(lua_State *luaState) {
	Proto *newProto = (Proto *)lua_malloc(luaState, sizeof(Proto));
	lua_linkObjToGC(luaState, obj2gco(newProto), LUA_TPROTO);

	newProto->k = NULL;
	newProto->sizek = 0;
	newProto->p = NULL;
	newProto->sizep = 0;
	newProto->code = NULL;
	newProto->sizecode = 0;
	newProto->sizelineinfo = 0;
	newProto->sizeupvalues = 0;
	newProto->nups = 0;
	newProto->upvalues = NULL;
	newProto->numparams = 0;
	newProto->is_vararg = 0;
	newProto->maxstacksize = 0;
	newProto->lineinfo = NULL;
	newProto->sizelocvars = 0;
	newProto->locvars = NULL;
	newProto->linedefined = 0;
	newProto->lastlinedefined = 0;
	newProto->source = NULL;

	return newProto;
}

TString *createString(lua_State *luaState, const char *str, size_t len) {
	TString *res;
	lua_pushlstring(luaState, str, len);

	res = rawtsvalue(luaState->top - 1);
	lua_pop(luaState, 1);

	return res;
}

Proto *makeFakeProto(lua_State *L, lu_byte nups) {
	Proto *p = createProto(L);

	p->sizelineinfo = 1;
	p->lineinfo = lua_newVector(L, 1, int);
	p->lineinfo[0] = 1;
	p->sizecode = 1;
	p->code = lua_newVector(L, 1, Instruction);
	p->code[0] = CREATE_ABC(OP_RETURN, 0, 1, 0);
	p->source = createString(L, "", 0);
	p->maxstacksize = 2;
	p->nups = nups;
	p->sizek = 0;
	p->sizep = 0;

	return p;
}

UpVal *createUpValue(lua_State *luaState, int stackpos) {
	UpVal *upValue = (UpVal *)lua_malloc(luaState, sizeof(UpVal));
	lua_linkObjToGC(luaState, (GCObject *)upValue, LUA_TUPVAL);
	upValue->tt = LUA_TUPVAL;
	upValue->v = &upValue->u.value;
	upValue->u.l.prev = NULL;
	upValue->u.l.next = NULL;

	const TValue *o2 = (TValue *)getObject(luaState, stackpos);
	upValue->v->value = o2->value;
	upValue->v->tt = o2->tt;
	checkliveness(G(L), upValue->v);

	return upValue;
}

void unboxUpValue(lua_State *luaState) {
	// >>>>> ...... func
	LClosure *lcl;
	UpVal *uv;

	lcl = (LClosure *)clvalue(getObject(luaState, -1));
	uv = lcl->upvals[0];

	lua_pop(luaState, 1);
	// >>>>> ......

	pushUpValue(luaState, uv);
	// >>>>> ...... upValue
}

size_t appendStackToStack_reverse(lua_State *from, lua_State *to) {
	for (StkId id = from->top - 1; id >= from->stack; --id) {
		setobj2s(to, to->top, id);
		to->top++;
	}

	return from->top - from->stack;
}

void correctStack(lua_State *L, TValue *oldstack) {
	CallInfo *ci;
	GCObject *up;
	L->top = (L->top - oldstack) + L->stack;
	for (up = L->openupval; up != NULL; up = up->gch.next)
		gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack;
	for (ci = L->base_ci; ci <= L->ci; ci++) {
		ci->top = (ci->top - oldstack) + L->stack;
		ci->base = (ci->base - oldstack) + L->stack;
		ci->func = (ci->func - oldstack) + L->stack;
	}
	L->base = (L->base - oldstack) + L->stack;
}

void lua_reallocstack(lua_State *L, int newsize) {
	TValue *oldstack = L->stack;
	int realsize = newsize + 1 + EXTRA_STACK;

	lua_reallocvector(L, L->stack, L->stacksize, realsize, TValue);
	L->stacksize = realsize;
	L->stack_last = L->stack + newsize;
	correctStack(L, oldstack);
}

void lua_reallocCallInfo(lua_State *lauState, int newsize) {
	CallInfo *oldci = lauState->base_ci;
	lua_reallocvector(lauState, lauState->base_ci, lauState->size_ci, newsize, CallInfo);

	lauState->size_ci = newsize;
	lauState->ci = (lauState->ci - oldci) + lauState->base_ci;
	lauState->end_ci = lauState->base_ci + lauState->size_ci - 1;
}

void GCUnlink(lua_State *luaState, GCObject *gco) {
	GCObject *prevslot;
	if (G(luaState)->rootgc == gco) {
		G(luaState)->rootgc = G(luaState)->rootgc->gch.next;
		return;
	}

	prevslot = G(luaState)->rootgc;
	while (prevslot->gch.next != gco) {
		prevslot = prevslot->gch.next;
	}

	prevslot->gch.next = prevslot->gch.next->gch.next;
}

TString *lua_newlstr(lua_State *luaState, const char *str, size_t len) {
	lua_pushlstring(luaState, str, len);
	TString *luaStr = &(luaState->top - 1)->value.gc->ts;

	lua_pop(luaState, 1);

	return luaStr;
}

void lua_link(lua_State *luaState, GCObject *o, lu_byte tt) {
	global_State *g = G(luaState);
	o->gch.next = g->rootgc;
	g->rootgc = o;
	o->gch.marked = luaC_white(g);
	o->gch.tt = tt;
}

Proto *lua_newproto(lua_State *luaState) {
	Proto *f = (Proto *)lua_malloc(luaState, sizeof(Proto));
	lua_link(luaState, obj2gco(f), LUA_TPROTO);
	f->k = NULL;
	f->sizek = 0;
	f->p = NULL;
	f->sizep = 0;
	f->code = NULL;
	f->sizecode = 0;
	f->sizelineinfo = 0;
	f->sizeupvalues = 0;
	f->nups = 0;
	f->upvalues = NULL;
	f->numparams = 0;
	f->is_vararg = 0;
	f->maxstacksize = 0;
	f->lineinfo = NULL;
	f->sizelocvars = 0;
	f->locvars = NULL;
	f->linedefined = 0;
	f->lastlinedefined = 0;
	f->source = NULL;
	return f;
}

UpVal *makeUpValue(lua_State *luaState, int stackPos) {
	UpVal *uv = lua_new(luaState, UpVal);
	lua_link(luaState, (GCObject *)uv, LUA_TUPVAL);
	uv->tt = LUA_TUPVAL;
	uv->v = &uv->u.value;
	uv->u.l.prev = NULL;
	uv->u.l.next = NULL;

	setobj(luaState, uv->v, getObject(luaState, stackPos));

	return uv;
}

void boxUpValue_start(lua_State *luaState) {
	LClosure *closure;
	closure = (LClosure *)lua_newLclosure(luaState, 1, hvalue(&luaState->l_gt));
	pushClosure(luaState, (Closure *)closure);
	// >>>>> ...... func
	closure->p = makeFakeProto(luaState, 1);

	// Temporarily initialize the upvalue to nil
	lua_pushnil(luaState);
	closure->upvals[0] = makeUpValue(luaState, -1);
	lua_pop(luaState, 1);
}

void boxUpValue_finish(lua_State *luaState) {
	// >>>>> ...... func obj
	LClosure *lcl = (LClosure *)clvalue(getObject(luaState, -2));

	lcl->upvals[0]->u.value = *getObject(luaState, -1);
	lua_pop(luaState, 1);
	// >>>>> ...... func
}

} // End of namespace Lua