// 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.Collections.Generic;
	using ULDebug = UniLua.Tools.ULDebug;
	using StringBuilder = System.Text.StringBuilder;
	using Char = System.Char;
	using Int32 = System.Int32;
    using System;

    internal static class LuaBaseLib
	{
		internal static int OpenLib( ILuaState lua )
		{
			NameFuncPair[] define = new NameFuncPair[]
			{
				new NameFuncPair( "assert", 		LuaBaseLib.B_Assert ),
				new NameFuncPair( "collectgarbage", LuaBaseLib.B_CollectGarbage ),
				new NameFuncPair( "dofile", 		LuaBaseLib.B_DoFile ),
				new NameFuncPair( "error", 			LuaBaseLib.B_Error ),
				new NameFuncPair( "ipairs", 		LuaBaseLib.B_Ipairs ),
				new NameFuncPair( "input", 			LuaBaseLib.B_Input ),
				new NameFuncPair( "loadfile", 		LuaBaseLib.B_LoadFile ),
				new NameFuncPair( "load", 			LuaBaseLib.B_Load ),
				new NameFuncPair( "loadstring", 	LuaBaseLib.B_Load ),
				new NameFuncPair( "next", 			LuaBaseLib.B_Next ),
				new NameFuncPair( "pairs", 			LuaBaseLib.B_Pairs ),
				new NameFuncPair( "pcall", 			LuaBaseLib.B_PCall ),
				new NameFuncPair( "print", 			LuaBaseLib.B_Print ),
				new NameFuncPair( "rawequal", 		LuaBaseLib.B_RawEqual ),
				new NameFuncPair( "rawlen", 		LuaBaseLib.B_RawLen ),
				new NameFuncPair( "rawget", 		LuaBaseLib.B_RawGet ),
				new NameFuncPair( "rawset", 		LuaBaseLib.B_RawSet ),
				new NameFuncPair( "select", 		LuaBaseLib.B_Select ),
				new NameFuncPair( "getmetatable", 	LuaBaseLib.B_GetMetaTable ),
				new NameFuncPair( "setmetatable", 	LuaBaseLib.B_SetMetaTable ),
				new NameFuncPair( "tonumber", 		LuaBaseLib.B_ToNumber ),
				new NameFuncPair( "tostring", 		LuaBaseLib.B_ToString ),
				new NameFuncPair( "type", 			LuaBaseLib.B_Type ),
				new NameFuncPair( "xpcall", 		LuaBaseLib.B_XPCall ),
			};

			// set global _G
			lua.PushGlobalTable();
			lua.PushGlobalTable();
			lua.SetField( -2, "_G" );

			// open lib into global lib
			lua.L_SetFuncs( define, 0 );
			// lua.RegisterGlobalFunc( "type", 	LuaBaseLib.B_Type );
			// lua.RegisterGlobalFunc( "pairs", 	LuaBaseLib.B_Pairs );
			// lua.RegisterGlobalFunc( "ipairs", 	LuaBaseLib.B_Ipairs );
			// lua.RegisterGlobalFunc( "print",	LuaBaseLib.B_Print );
			// lua.RegisterGlobalFunc( "tostring",	LuaBaseLib.B_ToString );

			lua.PushString( LuaDef.LUA_VERSION );
			lua.SetField( -2, "_VERSION" );

			return 1;
		}

		public static int B_Assert( ILuaState lua )
		{
			if( !lua.ToBoolean( 1 ) )
				return lua.L_Error( "{0}", lua.L_OptString( 2, "assertion failed!" ) );
			return lua.GetTop();
		}

		public static int B_CollectGarbage( ILuaState lua )
		{
			// not implement gc
			string opt = lua.L_OptString( 1, "collect" );
			switch( opt )
			{
				case "count":
					lua.PushNumber( 0 );
					lua.PushNumber( 0 );
					return 2;

				case "step":
				case "isrunning":
					lua.PushBoolean( true );
					return 1;

				default:
					lua.PushInteger( 0 );
					return 1;
			}
		}

		private static int DoFileContinuation( ILuaState lua )
		{
			return lua.GetTop() - 1;
		}

		public static int B_DoFile( ILuaState lua )
		{
			string filename = lua.L_OptString( 1, null );
			lua.SetTop( 1 );
			if( lua.L_LoadFile( filename ) != ThreadStatus.LUA_OK )
				lua.Error();
			lua.CallK( 0, LuaDef.LUA_MULTRET, 0, DoFileContinuation );
			return DoFileContinuation( lua );
		}

		public static int B_Error( ILuaState lua )
		{
			int level = lua.L_OptInt( 2, 1 );
			lua.SetTop( 1 );
			if( lua.IsString( 1 ) && level > 0 )
			{
				lua.L_Where( level );
				lua.PushValue( 1 );
				lua.Concat( 2 );
			}
			return lua.Error();
		}

		public static int B_Input( ILuaState lua )
		{
			string prompt = lua.L_OptString( 1, "> " );
			lua.SetTop( 1 );
			Console.Write( prompt );
			string input = Console.ReadLine();
			if( input != null )
			{
				lua.PushString( input );
				return 1;
			}
			lua.PushNil();
			return 1;
		}

		private static int LoadAux( ILuaState lua, ThreadStatus status, int envidx )
		{
			if( status == ThreadStatus.LUA_OK )
			{
				if( envidx != 0 ) // `env' parameter?
				{
					lua.PushValue(envidx); // push `env' on stack
					if( lua.SetUpvalue(-2, 1) == null ) // set `env' as 1st upvalue of loaded function
					{
						lua.Pop(1); // remove `env' if not used by previous call
					}
				}
				return 1;
			}
			else // error (message is on top of the stack)
			{
				lua.PushNil();
				lua.Insert(-2); // put before error message
				return 2; // return nil plus error message
			}
		}

		public static int B_LoadFile( ILuaState lua )
		{
			string fname = lua.L_OptString( 1, null );
			string mode  = lua.L_OptString( 2, null );
			int env = (!lua.IsNone(3) ? 3 : 0); // `env' index or 0 if no `env'
			var status = lua.L_LoadFileX( fname, mode );
			return LoadAux(lua, status, env);
		}

		public static int B_Load( ILuaState lua )
		{
			ThreadStatus status;
			string s = lua.ToString(1);
			string mode = lua.L_OptString(3, "bt");
			int env = (! lua.IsNone(4) ? 4 : 0); // `env' index or 0 if no `env'
			if( s != null )
			{
				string chunkName = lua.L_OptString(2, s);
				status = lua.L_LoadBufferX( s, chunkName, mode );
			}
			else // loading from a reader function
			{
				throw new System.NotImplementedException(); // TODO
			}
			return LoadAux( lua, status, env );
		}

		private static int FinishPCall( ILuaState lua, bool status )
		{
			// no space for extra boolean?
			if(!lua.CheckStack(1)) {
				lua.SetTop(0); // create space for return values
				lua.PushBoolean(false);
				lua.PushString("stack overflow");
				return 2;
			}
			lua.PushBoolean( status );
			lua.Replace( 1 );
			return lua.GetTop();
		}

		private static int PCallContinuation( ILuaState lua )
		{
			int context;
			ThreadStatus status = lua.GetContext( out context );
			return FinishPCall( lua, status == ThreadStatus.LUA_YIELD );
		}
		private static CSharpFunctionDelegate DG_PCallContinuation = PCallContinuation;

		public static int B_PCall( ILuaState lua )
		{
			lua.L_CheckAny( 1 );
			lua.PushNil();
			lua.Insert( 1 ); // create space for status result

			ThreadStatus status = lua.PCallK( lua.GetTop() - 2,
				LuaDef.LUA_MULTRET, 0, 0, DG_PCallContinuation );

			return FinishPCall( lua, status == ThreadStatus.LUA_OK );
		}

		public static int B_XPCall( ILuaState lua )
		{
			int n = lua.GetTop();
			lua.L_ArgCheck( n>=2, 2, "value expected" );
			lua.PushValue( 1 ); // exchange function...
			lua.Copy( 2, 1); // ...and error handler
			lua.Replace( 2 );
			ThreadStatus status = lua.PCallK( n-2, LuaDef.LUA_MULTRET,
				1, 0, DG_PCallContinuation );
			return FinishPCall( lua, status == ThreadStatus.LUA_OK );
		}

		public static int B_RawEqual( ILuaState lua )
		{
			lua.L_CheckAny( 1 );
			lua.L_CheckAny( 2 );
			lua.PushBoolean( lua.RawEqual( 1, 2 ) );
			return 1;
		}

		public static int B_RawLen( ILuaState lua )
		{
			LuaType t = lua.Type( 1 );
			lua.L_ArgCheck( t == LuaType.LUA_TTABLE || t == LuaType.LUA_TSTRING,
				1, "table or string expected" );
			lua.PushInteger( lua.RawLen( 1 ) );
			return 1;
		}

		public static int B_RawGet( ILuaState lua )
		{
			lua.L_CheckType( 1, LuaType.LUA_TTABLE );
			lua.L_CheckAny( 2 );
			lua.SetTop( 2 );
			lua.RawGet( 1 );
			return 1;
		}

		public static int B_RawSet( ILuaState lua )
		{
			lua.L_CheckType( 1, LuaType.LUA_TTABLE );
			lua.L_CheckAny( 2 );
			lua.L_CheckAny( 3 );
			lua.SetTop( 3 );
			lua.RawSet( 1 );
			return 1;
		}

		public static int B_Select( ILuaState lua )
		{
			int n = lua.GetTop();
			if( lua.Type( 1 ) == LuaType.LUA_TSTRING &&
				lua.ToString( 1 )[0] == '#' )
			{
				lua.PushInteger( n-1 );
				return 1;
			}
			else
			{
				int i = lua.L_CheckInteger( 1 );
				if( i < 0 ) i = n + i;
				else if( i > n ) i = n;
				lua.L_ArgCheck( 1 <= i, 1, "index out of range" );
				return n - i;
			}
		}

		public static int B_GetMetaTable( ILuaState lua )
		{
			lua.L_CheckAny( 1 );
			if( !lua.GetMetaTable( 1 ) )
			{
				lua.PushNil();
				return 1; // no metatable
			}
			lua.L_GetMetaField( 1, "__metatable" );
			return 1;
		}

		public static int B_SetMetaTable( ILuaState lua )
		{
			LuaType t = lua.Type( 2 );
			lua.L_CheckType( 1, LuaType.LUA_TTABLE );
			lua.L_ArgCheck( t == LuaType.LUA_TNIL || t == LuaType.LUA_TTABLE,
				2, "nil or table expected" );
			if( lua.L_GetMetaField( 1, "__metatable" ) )
				return lua.L_Error( "cannot change a protected metatable" );
			lua.SetTop( 2 );
			lua.SetMetaTable( 1 );
			return 1;
		}

		public static int B_ToNumber( ILuaState lua )
		{
			LuaType t = lua.Type( 2 );
			if( t == LuaType.LUA_TNONE || t == LuaType.LUA_TNIL ) // standard conversion
			{
				bool isnum;
				double n = lua.ToNumberX( 1, out isnum );
				if( isnum )
				{
					lua.PushNumber( n );
					return 1;
				} // else not a number; must be something
				lua.L_CheckAny( 1 );
			}
			else
			{
				string s = lua.L_CheckString( 1 );
				int numBase = lua.L_CheckInteger( 2 );
				bool negative = false;
				lua.L_ArgCheck( (2 <= numBase && numBase <= 36), 2,
					"base out of range" );
				s = s.Trim( ' ', '\f', '\n', '\r', '\t', '\v' );
				s = s + '\0'; // guard
				int pos = 0;
				if(s[pos] == '-') { pos++; negative = true; }
				else if(s[pos] == '+') pos++;
				if( Char.IsLetterOrDigit( s, pos ) )
				{
					double n = 0.0;
					do
					{
						int digit;
						if( Char.IsDigit( s, pos ) )
							digit = Int32.Parse( s[pos].ToString() );
						else
							digit = Char.ToUpper( s[pos] ) - 'A' + 10;
						if( digit >= numBase )
							break; // invalid numeral; force a fail
						n = n * (double)numBase + (double)digit;
						pos++;
					} while( Char.IsLetterOrDigit( s, pos ) );
					if( pos == s.Length - 1 ) // except guard, no invalid trailing characters?
					{
						lua.PushNumber( negative ? -n : n );
						return 1;
					} // else not a number
				} // else not a number
			}
			lua.PushNil(); // not a number
			return 1;
		}

		public static int B_Type( ILuaState lua )
		{
			var t = lua.Type( 1 );
			var tname = lua.TypeName( t );
			lua.PushString( tname );
			return 1;
		}

		private static int PairsMeta( ILuaState lua, string method, bool isZero
			, CSharpFunctionDelegate iter )
		{
			if( !lua.L_GetMetaField( 1, method ) ) // no metamethod?
			{
				lua.L_CheckType( 1, LuaType.LUA_TTABLE );
				lua.PushCSharpFunction( iter );
				lua.PushValue( 1 );
				if( isZero )
					lua.PushInteger( 0 );
				else
					lua.PushNil();
			}
			else
			{
				lua.PushValue( 1 );
				lua.Call( 1, 3 );
			}
			return 3;
		}

		public static int B_Next( ILuaState lua )
		{
			lua.SetTop( 2 );
			if( lua.Next(1) )
			{
				return 2;
			}
			else
			{
				lua.PushNil();
				return 1;
			}
		}
		static CSharpFunctionDelegate DG_B_Next = B_Next;

		public static int B_Pairs( ILuaState lua )
		{
			return PairsMeta( lua, "__pairs", false, DG_B_Next );
		}

		private static int IpairsAux( ILuaState lua )
		{
			int i = lua.ToInteger( 2 );
			i++; // next value
			lua.PushInteger( i );
			lua.RawGetI( 1, i );
			return lua.IsNil( -1 ) ? 1 : 2;
		}
		static CSharpFunctionDelegate DG_IpairsAux = IpairsAux;

		public static int B_Ipairs( ILuaState lua )
		{
			return PairsMeta( lua, "__ipairs", true, DG_IpairsAux );
		}

		public static int B_Print( ILuaState lua )
		{
			StringBuilder sb = new StringBuilder();
			int n = lua.GetTop();
			lua.GetGlobal( "tostring" );
			for( int i=1; i<=n; ++i )
			{
				lua.PushValue( -1 );
				lua.PushValue( i );
				lua.Call( 1, 1 );
				string s = lua.ToString( -1 );
				if( s == null )
					return lua.L_Error("'tostring' must return a string to 'print'");
				if( i > 1 )
					sb.Append( "\t" );
				sb.Append( s );
				lua.Pop( 1 );
			}
			Console.WriteLine( sb.ToString() );
			return 0;
		}

		public static int B_ToString( ILuaState lua )
		{
			lua.L_CheckAny( 1 );
			lua.L_ToString( 1 );
			return 1;
		}

	}

}

