// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
// 
// 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 <https://www.gnu.org/licenses/>.

// #define DEBUG_D_PRE_CALL
// #define DEBUG_D_POS_CALL

namespace UniLua
{
	using ULDebug = UniLua.Tools.ULDebug;
	using InstructionPtr = Pointer<Instruction>;
	using Exception = System.Exception;

	public class LuaRuntimeException : Exception
	{
		public ThreadStatus ErrCode { get; private set; }

		public LuaRuntimeException( ThreadStatus errCode )
		{
			ErrCode = errCode;
		}
	}

	public partial class LuaState
	{
		internal void D_Throw( ThreadStatus errCode )
		{
			throw new LuaRuntimeException( errCode );
		}

		private ThreadStatus D_RawRunProtected<T>( PFuncDelegate<T> func, ref T ud )
		{
			int oldNumCSharpCalls = NumCSharpCalls;
			ThreadStatus res = ThreadStatus.LUA_OK;
			try
			{
				func(ref ud);
			}
			catch( LuaRuntimeException e )
			{
				NumCSharpCalls = oldNumCSharpCalls;
				res = e.ErrCode;
			}
			NumCSharpCalls = oldNumCSharpCalls;
			return res;
		}

		private void SetErrorObj( ThreadStatus errCode, StkId oldTop )
		{
			switch( errCode )
			{
				case ThreadStatus.LUA_ERRMEM: // memory error?
					oldTop.V.SetSValue("not enough memory");
					break;

				case ThreadStatus.LUA_ERRERR:
					oldTop.V.SetSValue("error in error handling");
					break;

				default: // error message on current top
					oldTop.V.SetObj(ref Stack[Top.Index-1].V);
					break;
			}
			Top = Stack[oldTop.Index+1];
		}

		private ThreadStatus D_PCall<T>( PFuncDelegate<T> func, ref T ud,
			int oldTopIndex, int errFunc )
		{
			int oldCIIndex 			= CI.Index;
			bool oldAllowHook 		= AllowHook;
			int oldNumNonYieldable	= NumNonYieldable;
			int oldErrFunc			= ErrFunc;

			ErrFunc = errFunc;
			ThreadStatus status = D_RawRunProtected<T>( func, ref ud );
			if( status != ThreadStatus.LUA_OK ) // an error occurred?
			{
				F_Close( Stack[oldTopIndex] );
				SetErrorObj( status, Stack[oldTopIndex] );
				CI = BaseCI[oldCIIndex];
				AllowHook = oldAllowHook;
				NumNonYieldable = oldNumNonYieldable;
			}
			ErrFunc = oldErrFunc;
			return status;
		}

		private void D_Call( StkId func, int nResults, bool allowYield )
		{
			if( ++NumCSharpCalls >= LuaLimits.LUAI_MAXCCALLS )
			{
				if( NumCSharpCalls == LuaLimits.LUAI_MAXCCALLS )
					G_RunError( "CSharp Stack Overflow" );
				else if( NumCSharpCalls >=
						(LuaLimits.LUAI_MAXCCALLS + (LuaLimits.LUAI_MAXCCALLS>>3))
					)
					D_Throw( ThreadStatus.LUA_ERRERR );
			}
			if( !allowYield )
				NumNonYieldable++;
			if( !D_PreCall( func, nResults ) ) // is a Lua function ?
				V_Execute();
			if( !allowYield )
				NumNonYieldable--;
			NumCSharpCalls--;
		}

		/// <summary>
		/// return true if function has been executed
		/// </summary>
		private bool D_PreCall( StkId func, int nResults )
		{
			// prepare for Lua call

#if DEBUG_D_PRE_CALL
			Console.WriteLine( "============================ D_PreCall func:" + func );
#endif

			int funcIndex = func.Index;
			if(!func.V.TtIsFunction()) {
				// not a function
				// retry with `function' tag method
				func = tryFuncTM( func );

				// now it must be a function
				return D_PreCall( func, nResults );
			}

			if(func.V.ClIsLuaClosure()) {
				var cl = func.V.ClLValue();
				Utl.Assert(cl != null);
				var p = cl.Proto;

				D_CheckStack(p.MaxStackSize + p.NumParams);
				func = Stack[funcIndex];

				//     
				int n = (Top.Index - func.Index) - 1;
				for( ; n<p.NumParams; ++n )
					{ StkId.inc(ref Top).V.SetNilValue(); }

				int stackBase = (!p.IsVarArg) ? (func.Index + 1) : AdjustVarargs( p, n );
				
				CI = ExtendCI();
				CI.NumResults = nResults;
				CI.FuncIndex = func.Index;
				CI.BaseIndex = stackBase;
				CI.TopIndex  = stackBase + p.MaxStackSize;
				Utl.Assert(CI.TopIndex <= StackLast);
				CI.SavedPc = new InstructionPtr( p.Code, 0 );
				CI.CallStatus = CallStatus.CIST_LUA;

				Top = Stack[CI.TopIndex];

				return false;
			}

			if(func.V.ClIsCsClosure()) {
				var cscl = func.V.ClCsValue();
				Utl.Assert(cscl != null);

				D_CheckStack(LuaDef.LUA_MINSTACK);
				func = Stack[funcIndex];

				CI = ExtendCI();
				CI.NumResults = nResults;
				CI.FuncIndex = func.Index;
				CI.TopIndex = Top.Index + LuaDef.LUA_MINSTACK;
				CI.CallStatus = CallStatus.CIST_NONE;

				// do the actual call
				int n = cscl.F( this );
				
				// poscall
				D_PosCall( Top.Index-n );

				return true;
			}

			throw new System.NotImplementedException();
		}

		private int D_PosCall( int firstResultIndex )
		{
			// TODO: hook
			// be careful: CI may be changed after hook

			int resIndex = CI.FuncIndex;
			int wanted = CI.NumResults;

#if DEBUG_D_POS_CALL
			Console.WriteLine( "[D] ==== PosCall enter" );
			Console.WriteLine( "[D] ==== PosCall res:" + res );
			Console.WriteLine( "[D] ==== PosCall wanted:" + wanted );
#endif

			CI = BaseCI[CI.Index-1];

			int i = wanted;
			for( ; i!=0 && firstResultIndex < Top.Index; --i )
			{
#if DEBUG_D_POS_CALL
				Console.WriteLine( "[D] ==== PosCall assign lhs res:" + res );
				Console.WriteLine( "[D] ==== PosCall assign rhs firstResult:" + firstResult );
#endif
				Stack[resIndex++].V.SetObj(ref Stack[firstResultIndex++].V);
			}
			while( i-- > 0 )
			{
#if DEBUG_D_POS_CALL
				Console.WriteLine( "[D] ==== PosCall new LuaNil()" );
#endif
				Stack[resIndex++].V.SetNilValue();
			}
			Top = Stack[resIndex];
#if DEBUG_D_POS_CALL
			Console.WriteLine( "[D] ==== PosCall return " + (wanted - LuaDef.LUA_MULTRET) );
#endif
			return (wanted - LuaDef.LUA_MULTRET);
		}

		private CallInfo ExtendCI()
		{
			int newIndex = CI.Index + 1;
			if(newIndex >= BaseCI.Length) {
				int newLength = BaseCI.Length*2;
				var newBaseCI = new CallInfo[newLength];
				int i = 0;
				while(i < BaseCI.Length) {
					newBaseCI[i] = BaseCI[i];
					newBaseCI[i].List = newBaseCI;
					++i;
				}
				while(i < newLength) {
					var newCI = new CallInfo();
					newBaseCI[i] = newCI;
					newCI.List = newBaseCI;
					newCI.Index = i;
					++i;
				}
				BaseCI = newBaseCI;
				CI = newBaseCI[CI.Index];
				return newBaseCI[newIndex];
			}
			else {
				return BaseCI[newIndex];
			}
		}

		private int AdjustVarargs( LuaProto p, int actual )
		{
			//   `...'    
			//    : func (base)fixed-p1 fixed-p2 var-p1 var-p2 top
			//    : func nil            nil      var-p1 var-p2 (base)fixed-p1 fixed-p2 (reserved...) top
			//
			//    `...'    
			// func (base)fixed-p1 fixed-p2 (reserved...) top

			int NumFixArgs = p.NumParams;
			Utl.Assert( actual >= NumFixArgs, "AdjustVarargs (actual >= NumFixArgs) is false" );

			int fixedArg = Top.Index - actual; 	// first fixed argument
			int stackBase = Top.Index;		// final position of first argument
			for( int i=stackBase; i<stackBase+NumFixArgs; ++i )
			{
				Stack[i].V.SetObj(ref Stack[fixedArg].V);
				Stack[fixedArg++].V.SetNilValue();
			}
			Top = Stack[stackBase+NumFixArgs];
			return stackBase;
		}

		private StkId tryFuncTM( StkId func )
		{
			var tmObj = T_GetTMByObj( ref func.V, TMS.TM_CALL );
			if(!tmObj.V.TtIsFunction())
				G_TypeError( func, "call" );

			// open a hole inside the stack at `func'
			for(int i=Top.Index; i>func.Index; --i)
				{ Stack[i].V.SetObj(ref Stack[i-1].V); }

			IncrTop();
			func.V.SetObj(ref tmObj.V);
			return func;
		}

		private void D_CheckStack(int n)
		{
			if(StackLast - Top.Index <= n)
				D_GrowStack(n);
			// TODO: FOR DEBUGGING
			// else
			// 	CondMoveStack();
		}

		// some space for error handling
		private const int ERRORSTACKSIZE = LuaConf.LUAI_MAXSTACK + 200;

		private void D_GrowStack(int n)
		{
			int size = Stack.Length;
			if(size > LuaConf.LUAI_MAXSTACK)
				D_Throw(ThreadStatus.LUA_ERRERR);

			int needed = Top.Index + n + LuaDef.EXTRA_STACK;
			int newsize = 2 * size;
			if(newsize > LuaConf.LUAI_MAXSTACK)
				{ newsize = LuaConf.LUAI_MAXSTACK; }
			if(newsize < needed)
				{ newsize = needed; }
			if(newsize > LuaConf.LUAI_MAXSTACK)
			{
				D_ReallocStack(ERRORSTACKSIZE);
				G_RunError("stack overflow");
			}
			else
			{
				D_ReallocStack(newsize);
			}
		}

		private void D_ReallocStack(int size)
		{
			Utl.Assert(size <= LuaConf.LUAI_MAXSTACK || size == ERRORSTACKSIZE);
			var newStack = new StkId[size];
			int i = 0;
			for( ; i<Stack.Length; ++i) {
				newStack[i] = Stack[i];
				newStack[i].SetList(newStack);
			}
			for( ; i<size; ++i) {
				newStack[i] = new StkId();
				newStack[i].SetList(newStack);
				newStack[i].SetIndex(i);
				newStack[i].V.SetNilValue();
			}
			Top = newStack[Top.Index];
			Stack = newStack;
			StackLast = size - LuaDef.EXTRA_STACK;
		}
	}

}

