// 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/>.

namespace UniLua
{
	using System;
	using System.IO;
	using System.Collections.Generic;
    using System.Text;
    using System.Xml.Linq;
    using Cosmos.HAL.Drivers.Video.SVGAII;

	public struct NameFuncPair
	{
		public string Name;
		public CSharpFunctionDelegate Func;

		public NameFuncPair( string name, CSharpFunctionDelegate func )
		{
			Name = name;
			Func = func;
		}
	}

	public interface ILuaAuxLib
	{
		void 	L_Where( int level );
		int 	L_Error( string fmt, params object[] args );
		void	L_CheckStack( int size, string msg );
		void 	L_CheckAny( int narg );
		void 	L_CheckType( int index, LuaType t );
		double	L_CheckNumber( int narg );
		UInt64	L_CheckUInt64( int narg );
		int 	L_CheckInteger( int narg );
		string 	L_CheckString( int narg );
		uint	L_CheckUnsigned( int narg );
		void 	L_ArgCheck( bool cond, int narg, string extraMsg );
		int 	L_ArgError( int narg, string extraMsg );
		string 	L_TypeName( int index );

		string 	L_ToString( int index );
		bool 	L_GetMetaField( int index, string method );
		int 	L_GetSubTable( int index, string fname );

		void 	L_RequireF( string moduleName, CSharpFunctionDelegate openFunc, bool global );
		void 	L_OpenLibs();
		void 	L_NewLibTable( NameFuncPair[] define );
		void	L_NewLib( NameFuncPair[] define );
		void 	L_SetFuncs( NameFuncPair[] define, int nup );

        int L_Opt(int n, int def);

        int		L_OptInt( int narg, int def );
		string 	L_OptString( int narg, string def );
		bool 	L_CallMeta( int obj, string name );
		void	L_Traceback( ILuaState otherLua, string msg, int level );
		int		L_Len( int index );

		ThreadStatus L_LoadBuffer( string s, string name );
		ThreadStatus L_LoadBufferX( string s, string name, string mode );
		ThreadStatus L_LoadFile( string filename );
		ThreadStatus L_LoadFileX( string filename, string mode );

		ThreadStatus L_LoadString( string s );
		ThreadStatus L_LoadBytes( byte[] bytes, string name);
        ThreadStatus L_DoByteArray(byte[] file, string name);
        ThreadStatus L_DoString( string s );
		ThreadStatus L_DoFile( string filename );
		

		string	L_Gsub( string src, string pattern, string rep );

		// reference system
		int		L_Ref( int t );
		void	L_Unref( int t, int reference );
	}

	class StringLoadInfo : ILoadInfo
	{
		public StringLoadInfo(string s )
		{
			Str = s;
			Pos = 0;
		}

		public int ReadByte()
		{
			if( Pos >= Str.Length )
				return -1;
			else
				return Str[Pos++];
		}

		public int PeekByte()
		{
			if( Pos >= Str.Length )
				return -1;
			else
				return Str[Pos];
		}

		private string 	Str;
		private int		Pos;
	}

	class BytesLoadInfo : ILoadInfo
	{
		public BytesLoadInfo( byte[] bytes )
		{
			Bytes 	= bytes;
			Pos 	= 0;
		}

		public int ReadByte()
		{
			if( Pos >= Bytes.Length )
				return -1;
			else
				return Bytes[Pos++];
		}

		public int PeekByte()
		{
			if( Pos >= Bytes.Length )
				return -1;
			else
				return Bytes[Pos];
		}

		private byte[] 	Bytes;
		private int 	Pos;
	}

	public partial class LuaState
	{
		private const int LEVELS1 = 12; // size of the first part of the stack
		private const int LEVELS2 = 10; // size of the second part of the stack

		public void L_Where( int level )
		{
			LuaDebug ar = new LuaDebug();
			if( API.GetStack( level, ar ) ) // check function at level
			{
				GetInfo( "Sl", ar ); // get info about it
				if( ar.CurrentLine > 0 ) // is there info?
				{
					API.PushString( string.Format( "{0}:{1}: ", ar.ShortSrc, ar.CurrentLine ) );
					return;
				}
			}
			API.PushString( "" ); // else, no information available...
		}

		public int L_Error( string fmt, params object[] args )
		{
			L_Where( 1 );
			API.PushString( string.Format( fmt, args ) );
			API.Concat( 2 );
			return API.Error();
		}

		public void L_CheckStack( int size, string msg )
		{
			// keep some extra space to run error routines, if needed
			if(!API.CheckStack(size + LuaDef.LUA_MINSTACK)) {
				if(msg != null)
					{ L_Error(string.Format("stack overflow ({0})", msg)); }
				else
					{ L_Error("stack overflow"); }
			}
		}

		public void L_CheckAny( int narg )
		{
			if( API.Type( narg ) == LuaType.LUA_TNONE )
				L_ArgError( narg, "value expected" );
		}

		public double L_CheckNumber( int narg )
		{
			bool isnum;
			double d = API.ToNumberX( narg, out isnum );
			if( !isnum )
				TagError( narg, LuaType.LUA_TNUMBER );
			return d;
		}

		public UInt64 L_CheckUInt64( int narg )
		{
			bool isnum;
			UInt64 v = API.ToUInt64X( narg, out isnum );
			if( !isnum )
				TagError( narg, LuaType.LUA_TUINT64 );
			return v;
		}

		public int L_CheckInteger( int narg )
		{
			bool isnum;
			int d = API.ToIntegerX( narg, out isnum );
			if( !isnum )
				TagError( narg, LuaType.LUA_TNUMBER );
			return d;
		}

		public string L_CheckString( int narg )
		{
			string s = API.ToString( narg );
			if( s == null ) TagError( narg, LuaType.LUA_TSTRING );
			return s;
		}

		public uint L_CheckUnsigned( int narg )
		{
			bool isnum;
			uint d = API.ToUnsignedX( narg, out isnum );
			if( !isnum )
				TagError( narg, LuaType.LUA_TNUMBER );
			return d;
		}

        public int L_Opt(int n, int def)
        {
            LuaType t = API.Type(n);
            if (t == LuaType.LUA_TNONE || t == LuaType.LUA_TNIL)
            {
                return def;
            }
            else
            {
                return L_CheckInteger(n);
            }
        }

        public int L_OptInt( int narg, int def )
		{
			LuaType t = API.Type( narg );
			if( t == LuaType.LUA_TNONE ||
				t == LuaType.LUA_TNIL )
			{
				return def;
			}
			else
			{
				return L_CheckInteger( narg );
			}
		}

		public string L_OptString( int narg, string def )
		{
			LuaType t = API.Type( narg );
			if( t == LuaType.LUA_TNONE ||
				t == LuaType.LUA_TNIL )
			{
				return def;
			}
			else
			{
				return L_CheckString( narg );
			}
		}

		private int TypeError( int index, string typeName )
		{
			string msg = string.Format( "{0} expected, got {1}",
				typeName, L_TypeName( index ) );
			API.PushString( msg );
			return L_ArgError( index, msg );
		}

		private void TagError( int index, LuaType t )
		{
			TypeError( index, API.TypeName( t ) );
		}

		public void L_CheckType( int index, LuaType t )
		{
			if( API.Type( index ) != t )
				TagError( index, t );
		}

		public void L_ArgCheck( bool cond, int narg, string extraMsg )
		{
			if( !cond )
				L_ArgError( narg, extraMsg );
		}

		public int L_ArgError( int narg, string extraMsg )
		{

			LuaDebug ar = new LuaDebug();
			if( !API.GetStack( 0, ar ) ) // no stack frame ?
				return L_Error( "bad argument {0} ({1})", narg, extraMsg );

			GetInfo( "n", ar );
			if( ar.NameWhat == "method" )
			{
				narg--; // do not count 'self'
				if( narg == 0 ) // error is in the self argument itself?
					return L_Error( "calling '{0}' on bad self", ar.Name );
			}
			if( ar.Name == null )
				ar.Name = PushGlobalFuncName( ar ) ? API.ToString(-1) : "?";
			return L_Error( "bad argument {0} to '{1}' ({2})",
				narg, ar.Name, extraMsg );
		}

		public string L_TypeName( int index )
		{
			return API.TypeName( API.Type( index ) );
		}

		public bool L_GetMetaField( int obj, string name )
		{
			if( !API.GetMetaTable(obj) ) // no metatable?
				return false;
			API.PushString( name );
			API.RawGet( -2 );
			if( API.IsNil( -1 ) )
			{
				API.Pop( 2 );
				return false;
			}
			else
			{
				API.Remove( -2 );
				return true;
			}
		}

		public bool L_CallMeta( int obj, string name )
		{
			obj = API.AbsIndex( obj );
			if( !L_GetMetaField( obj, name ) ) // no metafield?
				return false;

			API.PushValue( obj );
			API.Call( 1, 1 );
			return true;
		}

		private void PushFuncName( LuaDebug ar )
		{
			if( ar.NameWhat.Length > 0 && ar.NameWhat[0] != '\0' ) // is there a name?
				API.PushString( string.Format( "function '{0}'", ar.Name ) );
			else if( ar.What.Length > 0 && ar.What[0] == 'm' ) // main?
				API.PushString( "main chunk" );
			else if( ar.What.Length > 0 && ar.What[0] == 'C' )
			{
				if( PushGlobalFuncName( ar ) )
				{
					API.PushString( string.Format( "function '{0}'", API.ToString(-1) ) );
					API.Remove( -2 ); //remove name
				}
				else
					API.PushString( "?" );
			}
			else
				API.PushString( string.Format( "function <{0}:{1}>", ar.ShortSrc, ar.LineDefined ) );
		}

		private int CountLevels()
		{
			LuaDebug ar = new LuaDebug();
			int li = 1;
			int le = 1;
			// find an upper bound
			while( API.GetStack(le, ar) )
			{
				li = le;
				le *= 2;
			}
			// do a binary search
			while(li < le)
			{
				int m = (li + le)/2;
				if( API.GetStack( m, ar ) )
					li = m + 1;
				else
					le = m;
			}
			return le - 1;
		}

		public void L_Traceback( ILuaState otherLua, string msg, int level )
		{
			LuaState oLua = otherLua as LuaState;
			LuaDebug ar = new LuaDebug();
			int top = API.GetTop();
			int numLevels = oLua.CountLevels();
			int mark = (numLevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0;
			if( msg != null )
				API.PushString( string.Format( "{0}\n", msg ) );
			API.PushString( "stack traceback:" );
			while( otherLua.GetStack( level++, ar ) )
			{
				if( level == mark ) // too many levels?
				{
					API.PushString( "\n\t..." );
					level = numLevels - LEVELS2; // and skip to last ones
				}
				else
				{
					oLua.GetInfo( "Slnt", ar );
					API.PushString( string.Format( "\n\t{0}:", ar.ShortSrc ) );
					if( ar.CurrentLine > 0 )
						API.PushString( string.Format( "{0}:", ar.CurrentLine ) );
					API.PushString(" in ");
					PushFuncName( ar );
					if( ar.IsTailCall )
						API.PushString( "\n\t(...tail calls...)" );
					API.Concat( API.GetTop() - top );
				}
			}
			API.Concat( API.GetTop() - top );
		}

		public int L_Len( int index )
		{
			API.Len( index );

			bool isnum;
			int l = (int)API.ToIntegerX( -1, out isnum );
			if( !isnum )
				L_Error( "object length is not a number" );
			API.Pop( 1 );
			return l;
		}

		public ThreadStatus L_LoadBuffer( string s, string name )
		{
			return L_LoadBufferX( s, name, null );
		}

		public ThreadStatus L_LoadBufferX( string s, string name, string mode )
		{
			var loadinfo = new StringLoadInfo( s );
			return API.Load( loadinfo, name, mode );
		}

		public ThreadStatus L_LoadBytes( byte[] bytes, string name)
		{
            LuaFile.VirtualFiles.Add(name, bytes);

            var loadinfo = new BytesLoadInfo( bytes );
			return API.Load( loadinfo, name, null );
		}

		private ThreadStatus ErrFile( string what, int fnameindex )
		{
			return ThreadStatus.LUA_ERRFILE;
		}

		public ThreadStatus L_LoadFile( string filename )
		{
			return L_LoadFileX( filename, null );
		}

		public ThreadStatus L_LoadFileX( string filename, string mode )
		{
			var status = ThreadStatus.LUA_OK;
			if( filename == null )
			{
				//       stdin   
				throw new System.NotImplementedException();
			}

			int fnameindex = API.GetTop() + 1;
			API.PushString( "@" + filename );
			try
			{
				var loadinfo = LuaFile.OpenFile(filename);

                if (loadinfo.GetType() == typeof(FileLoadInfo))
                {
                    ((FileLoadInfo)loadinfo).SkipComment();
                    status = API.Load(((FileLoadInfo)loadinfo), API.ToString(-1), mode);
                    ((FileLoadInfo)loadinfo).Dispose();
                }
                else if (loadinfo.GetType() == typeof(BytesLoadInfo))
                {
                    status = API.Load(loadinfo, filename, null);
                }
            }
			catch( LuaRuntimeException e )
			{
				API.PushString( string.Format( "cannot open {0}: {1}",
					filename, e.Message ) );
				return ThreadStatus.LUA_ERRFILE;
			}

			API.Remove( fnameindex );
			return status;
		}

		public ThreadStatus L_LoadString( string s )
		{
			return L_LoadBuffer( s, s );
		}

        public ThreadStatus L_DoByteArray(byte[] file, string name)
        {
            var status = L_LoadBytes(file, name);
            if (status != ThreadStatus.LUA_OK)
                return status;
            return API.PCall(0, LuaDef.LUA_MULTRET, 0);
        }

        public ThreadStatus L_DoString( string s )
		{
			var status = L_LoadString( s );
			if( status != ThreadStatus.LUA_OK )
				return status;
			return API.PCall( 0, LuaDef.LUA_MULTRET, 0 );
		}

		public ThreadStatus L_DoFile( string filename )
		{
			var status = L_LoadFile( filename );
			if( status != ThreadStatus.LUA_OK )
				return status;
			return API.PCall( 0, LuaDef.LUA_MULTRET, 0 );
		}

		public string L_Gsub( string src, string pattern, string rep )
		{
			string res = src.Replace(pattern, rep);
			API.PushString( res );
			return res;
		}
		
		public string L_ToString( int index )
		{
			if( !L_CallMeta( index, "__tostring" ) ) // no metafield? // TODO L_CallMeta
			{
				switch( API.Type(index) )
				{
					case LuaType.LUA_TNUMBER:
					case LuaType.LUA_TSTRING:
						API.PushValue( index );
						break;

					case LuaType.LUA_TBOOLEAN:
						API.PushString( API.ToBoolean( index ) ? "true" : "false" );
						break;

					case LuaType.LUA_TNIL:
						API.PushString( "nil" );
						break;

					default:
						API.PushString( string.Format("{0}: {1:X}"
							, L_TypeName( index )
							, API.ToObject( index ).GetHashCode()
							) );
						break;
				}
			}
			return API.ToString( -1 );
		}

		// private static class LibLoadInfo
		// {
		// 	public static List<NameFuncPair> Items;

		// 	static LibLoadInfo()
		// 	{
		// 		Items = new List<NameFuncPair>();
		// 		Add( "_G", LuaState.LuaOpen_Base );
		// 	}

		// 	private static void Add( string name, CSharpFunctionDelegate loadFunc )
		// 	{
		// 		Items.Add( new NameFuncPair { Name=name, LoadFunc=loadFunc } );
		// 	}
		// }

		public void L_OpenLibs()
		{
			NameFuncPair[] define = new NameFuncPair[]
			{
				new NameFuncPair( "_G", 				LuaBaseLib.OpenLib  ),
				new NameFuncPair( LuaPkgLib.LIB_NAME,	LuaPkgLib.OpenLib 	),
				new NameFuncPair( LuaCoroLib.LIB_NAME,	LuaCoroLib.OpenLib  ),
				new NameFuncPair( LuaTableLib.LIB_NAME,	LuaTableLib.OpenLib ),
				new NameFuncPair( LuaIOLib.LIB_NAME,	LuaIOLib.OpenLib	),
				new NameFuncPair( LuaOSLib.LIB_NAME,	LuaOSLib.OpenLib	),
				// {LUA_OSLIBNAME, luaopen_os},
				new NameFuncPair( LuaStrLib.LIB_NAME, 	LuaStrLib.OpenLib   ),
				new NameFuncPair( LuaBitLib.LIB_NAME, 	LuaBitLib.OpenLib   ),
				new NameFuncPair( LuaMathLib.LIB_NAME, 	LuaMathLib.OpenLib  ),
				new NameFuncPair( LuaDebugLib.LIB_NAME, LuaDebugLib.OpenLib ),
				new NameFuncPair( LuaEncLib.LIB_NAME,	LuaEncLib.OpenLib	),
				new NameFuncPair( LuaJsonLib.LIB_NAME,	LuaJsonLib.OpenLib	),
            };

			for( var i=0; i<define.Length; ++i)
			{
				L_RequireF( define[i].Name, define[i].Func, true );
				API.Pop( 1 );
			}

			// LuaBaseLib.LuaOpen_Base( this );
		}

		public void L_RequireF( string moduleName, CSharpFunctionDelegate openFunc, bool global )
		{
			API.PushCSharpFunction( openFunc );
			API.PushString( moduleName );
			API.Call( 1, 1 );
			L_GetSubTable( LuaDef.LUA_REGISTRYINDEX, "_LOADED" );
			API.PushValue( -2 );
			API.SetField( -2, moduleName );
			API.Pop( 1 );
			if( global )
			{
				API.PushValue( -1 );
				API.SetGlobal( moduleName );
			}
		}

		public int L_GetSubTable( int index, string fname )
		{
			API.GetField( index, fname );
			if( API.IsTable( -1 ) )
				return 1;
			else
			{
				API.Pop( 1 );
				index = API.AbsIndex( index );
				API.NewTable();
				API.PushValue( -1 );
				API.SetField( index, fname );
				return 0;
			}
		}

		public void L_NewLibTable( NameFuncPair[] define )
		{
			API.CreateTable( 0, define.Length );
		}

		public void L_NewLib( NameFuncPair[] define )
		{
			L_NewLibTable( define );
			L_SetFuncs( define, 0 );
		}

		public void L_SetFuncs( NameFuncPair[] define, int nup )
		{
			// TODO: Check Version
			L_CheckStack(nup, "too many upvalues");
			for( var j=0; j<define.Length; ++j )
			{
				for( int i=0; i<nup; ++i )
					API.PushValue( -nup );
				API.PushCSharpClosure( define[j].Func, nup );
				API.SetField( -(nup + 2), define[j].Name );
			}
			API.Pop( nup );
		}

		private bool FindField( int objIndex, int level )
		{
			if( level == 0 || !API.IsTable(-1) )
				return false; // not found

			API.PushNil(); // start 'next' loop
			while( API.Next(-2) ) // for each pair in table
			{
				if( API.Type(-2) == LuaType.LUA_TSTRING ) // ignore non-string keys
				{
					if( API.RawEqual( objIndex, -1 ) ) // found object?
					{
						API.Pop(1); // remove value (but keep name)
						return true;
					}
					else if( FindField( objIndex, level-1 ) ) // try recursively
					{
						API.Remove( -2 ); // remove table (but keep name)
						API.PushString( "." );
						API.Insert( -2 ); // place '.' between the two names
						API.Concat( 3 );
						return true;
					}
				}
				API.Pop(1); // remove value
			}
			return false; // not found
		}

		private bool PushGlobalFuncName( LuaDebug ar )
		{
			int top = API.GetTop();
			GetInfo( "f", ar );
			API.PushGlobalTable();
			if( FindField( top+1, 2 ) )
			{
				API.Copy( -1, top+1 );
				API.Pop( 2 );
				return true;
			}
			else
			{
				API.SetTop( top ); // remove function and global table
				return false;
			}
		}

		private const int FreeList = 0;

		public int L_Ref( int t )
		{
			if( API.IsNil(-1) )
			{
				API.Pop(1); // remove from stack
				return LuaConstants.LUA_REFNIL; // `nil' has a unique fixed reference
			}

			t = API.AbsIndex(t);
			API.RawGetI(t, FreeList); // get first free element
			int reference = API.ToInteger(-1); // ref = t[freelist]
			API.Pop(1); // remove it from stack
			if( reference != 0 ) // any free element?
			{
				API.RawGetI(t, reference); // remove it from list
				API.RawSetI(t, FreeList); // t[freelist] = t[ref]
			}
			else // no free elements
				reference = API.RawLen(t) + 1; // get a new reference
			API.RawSetI(t, reference);
			return reference;
		}

		public void L_Unref( int t, int reference )
		{
			if( reference >= 0 )
			{
				t = API.AbsIndex(t);
				API.RawGetI(t, FreeList);
				API.RawSetI(t, reference); // t[ref] = t[freelist]
				API.PushInteger(reference);
				API.RawSetI(t, FreeList); // t[freelist] = ref
			}
		}

#if UNITY_IPHONE
        public void FEED_AOT_FOR_IOS(LuaState lua)
        {
            lua.L_Opt( lua.L_CheckInteger, 1, 1);
        }
#endif

	}

}

