//-----------------------------------------------------------------------------
//
// Copyright 2018 Christoph Oelckers
//
// 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 3 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, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
//
// DESCRIPTION: generalized interface for implementing ACS/FS functions
// in ZScript.
//
//-----------------------------------------------------------------------------


#include "tarray.h"
#include "dobject.h"
#include "vm.h"
#include "scriptutil.h"
#include "p_acs.h"
#include "actorinlines.h"


static TArray<VMValue> parameters;
static TMap<FName, VMFunction*> functions;

void ScriptUtil::Clear()
{
	parameters.Clear();
	functions.Clear();
}

void ScriptUtil::BuildParameters(va_list ap)
{
	for(int type = va_arg(ap, int); type != End; type = va_arg(ap, int))
	{
		switch (type)
		{
			case Int:
				parameters.Push(VMValue(va_arg(ap, int)));
				break;
				
			case Pointer:
			case Class:		// this is just a pointer.
			case String:	// must be passed by reference to a persistent location!
				parameters.Push(VMValue(va_arg(ap, void*)));
				break;
				
			case Float:
				parameters.Push(VMValue(va_arg(ap, double)));
				break;
		}
	}
}

void ScriptUtil::RunFunction(FName functionname, unsigned paramstart, VMReturn &returns)
{
	VMFunction *func = nullptr;
	auto check = functions.CheckKey(functionname);
	if (!check)
	{
		func = PClass::FindFunction(NAME_ScriptUtil, functionname);
		if (func == nullptr) 
		{
			I_Error("Call to undefined function ScriptUtil.%s", functionname.GetChars());
		}
		functions.Insert(functionname, func);
	}
	else func = *check;

	VMCall(func, &parameters[paramstart], parameters.Size() - paramstart, &returns, 1);
}

int ScriptUtil::Exec(FName functionname, ...)
{
	unsigned paramstart = parameters.Size();
	va_list ap;
	va_start(ap, functionname);
	try
	{
		BuildParameters(ap);
		int ret = 0;
		VMReturn returns(&ret);
		RunFunction(functionname, paramstart, returns);
		va_end(ap);
		parameters.Clamp(paramstart);
		return ret;
	}
	catch(...)
	{
		va_end(ap);
		parameters.Clamp(paramstart);
		throw;
	}
}
