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

namespace UniLua
{
	using System;
	using System.Collections.Generic;
	using ULDebug = UniLua.Tools.ULDebug;

	public struct TValue
	{
		private const UInt64 CLOSURE_LUA = 0; // lua closure
		private const UInt64 CLOSURE_CS = 1; // c# closure
		private const UInt64 CLOSURE_LCS = 2; // light c# closure

		private const UInt64 BOOLEAN_FALSE = 0;
		private const UInt64 BOOLEAN_TRUE = 1;

		public int Tt;
		public double NValue;
		public UInt64 UInt64Value;
		public object OValue;
#if DEBUG_DUMMY_TVALUE_MODIFY
		public bool Lock_;
#endif

		public override int GetHashCode()
		{
			return Tt.GetHashCode() ^ NValue.GetHashCode()
				^ UInt64Value.GetHashCode()
				^ (OValue != null ? OValue.GetHashCode() : 0x12345678);
		}
		public override bool Equals(object o)
		{
			if(!(o is TValue)) return false;
			return Equals((TValue)o);
		}
		public bool Equals(TValue o)
		{
			if(Tt != o.Tt || NValue != o.NValue || UInt64Value != o.UInt64Value)
				{ return false; }

			switch(Tt) {
				case (int)LuaType.LUA_TNIL: return true;
				case (int)LuaType.LUA_TBOOLEAN: return BValue() == o.BValue();
				case (int)LuaType.LUA_TNUMBER: return NValue == o.NValue;
				case (int)LuaType.LUA_TUINT64: return UInt64Value == o.UInt64Value;
				case (int)LuaType.LUA_TSTRING: return SValue() == o.SValue();
				default: return System.Object.ReferenceEquals(OValue, o.OValue);
			}
		}
		public static bool operator==(TValue lhs, TValue rhs)
		{
			return lhs.Equals(rhs);
		}
		public static bool operator!=(TValue lhs, TValue rhs)
		{
			return !lhs.Equals(rhs);
		}

#if DEBUG_DUMMY_TVALUE_MODIFY
		private void CheckLock() {
			if(Lock_) {
				UnityEngine.Console.WriteLineError("changing a lock value");
			}
		}
#endif

		internal bool TtIsNil() { return Tt == (int)LuaType.LUA_TNIL; }
		internal bool TtIsBoolean() { return Tt == (int)LuaType.LUA_TBOOLEAN; }
		internal bool TtIsNumber() { return Tt == (int)LuaType.LUA_TNUMBER; }
		internal bool TtIsUInt64() { return Tt == (int)LuaType.LUA_TUINT64; }
		internal bool TtIsString() { return Tt == (int)LuaType.LUA_TSTRING; }
		internal bool TtIsTable() { return Tt == (int)LuaType.LUA_TTABLE; }
		internal bool TtIsFunction() { return Tt == (int)LuaType.LUA_TFUNCTION; }
		internal bool TtIsThread() { return Tt == (int)LuaType.LUA_TTHREAD; }

		internal bool ClIsLuaClosure() { return UInt64Value == CLOSURE_LUA; }
		internal bool ClIsCsClosure() { return UInt64Value == CLOSURE_CS; }
		internal bool ClIsLcsClosure() { return UInt64Value == CLOSURE_LCS; }

		internal bool BValue() { return UInt64Value != BOOLEAN_FALSE; }
		internal string SValue() { return (string)OValue; }
		internal LuaTable HValue() { return OValue as LuaTable; }
		internal LuaLClosureValue ClLValue() { return (LuaLClosureValue)OValue; }
		internal LuaCsClosureValue ClCsValue() { return (LuaCsClosureValue)OValue; }
		internal LuaUserDataValue RawUValue() { return OValue as LuaUserDataValue; }

		internal void SetNilValue() {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TNIL;
			NValue = 0.0;
			UInt64Value = 0;
			OValue = null;
		}
		internal void SetBValue(bool v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TBOOLEAN;
			NValue = 0.0;
			UInt64Value = v ? BOOLEAN_TRUE : BOOLEAN_FALSE;
			OValue = null;
		}
		internal void SetObj(ref TValue v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = v.Tt;
			NValue = v.NValue;
			UInt64Value = v.UInt64Value;
			OValue = v.OValue;
		}
		internal void SetNValue(double v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TNUMBER;
			NValue = v;
			UInt64Value = 0;
			OValue = null;
		}
		internal void SetUInt64Value(UInt64 v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TUINT64;
			NValue = 0.0;
			UInt64Value = v;
			OValue = null;
		}
		internal void SetSValue(string v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TSTRING;
			NValue = 0.0;
			UInt64Value = 0;
			OValue = v;
		}
		internal void SetHValue(LuaTable v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TTABLE;
			NValue = 0.0;
			UInt64Value = 0;
			OValue = v;
		}
		internal void SetThValue(LuaState v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TTHREAD;
			NValue = 0.0;
			UInt64Value = 0;
			OValue = v;
		}
		internal void SetPValue(object v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TLIGHTUSERDATA;
			NValue = 0.0;
			UInt64Value = 0;
			OValue = v;
		}
		internal void SetClLValue(LuaLClosureValue v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TFUNCTION;
			NValue = 0.0;
			UInt64Value = CLOSURE_LUA;
			OValue = v;
		}
		internal void SetClCsValue(LuaCsClosureValue v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TFUNCTION;
			NValue = 0.0;
			UInt64Value = CLOSURE_CS;
			OValue = v;
		}
		internal void SetClLcsValue(CSharpFunctionDelegate v) {
#if DEBUG_DUMMY_TVALUE_MODIFY
			CheckLock();
#endif
			Tt = (int)LuaType.LUA_TFUNCTION;
			NValue = 0.0;
			UInt64Value = CLOSURE_LCS;
			OValue = v;
		}

		public override string ToString()
		{
			if (TtIsString()) {
				return string.Format("(string, {0})", SValue());
			} else if (TtIsNumber()) {
				return string.Format("(number, {0})", NValue);
			} else if (TtIsNil()) {
				return "(nil)";
			} else {
				return string.Format("(type:{0})", Tt);
			}
		}
	}

	public class StkId
	{
		public TValue V;

		private StkId[] List;
		public int Index { get; private set; }

		public void SetList(StkId[] list) { List = list; }
		public void SetIndex(int index) { Index = index; }

		public static StkId inc(ref StkId val)
		{
			var ret = val;
			val = val.List[val.Index+1];
			return ret;
		}

		public override string ToString()
		{
			string detail;
			if(V.TtIsString())
				{ detail = V.SValue().Replace("\n", " "); }
			else
				{ detail = "..."; }
			return string.Format("StkId - {0} - {1}", LuaState.TypeName((LuaType)V.Tt), detail);
		}
	}

	public class LuaLClosureValue
	{
		public LuaProto 		Proto;
		public LuaUpvalue[]		Upvals;

		public LuaLClosureValue(LuaProto p) {
			Proto = p;

			Upvals = new LuaUpvalue[p.Upvalues.Count];
			for(int i=0; i<p.Upvalues.Count; ++i)
				{ Upvals[i] = new LuaUpvalue(); }
		}
	}
	
	public class LuaUserDataValue
	{
		public object Value;
		public LuaTable MetaTable;
		public int Length;
	}

	public class LocVar
	{
		public string VarName;
		public int StartPc;
		public int EndPc;
	}

	public class UpvalDesc
	{
		public string Name;
		public int Index;
		public bool InStack;
	}

	public class LuaProto
	{
		public List<Instruction> 	Code;
		public List<StkId>			K;
		public List<LuaProto>		P;
		public List<UpvalDesc>		Upvalues;

		public int					LineDefined;
		public int					LastLineDefined;

		public int					NumParams;
		public bool					IsVarArg;
		public byte					MaxStackSize;

		public string				Source;
		public List<int>			LineInfo;
		public List<LocVar>			LocVars;

		public LuaProto()
		{
			Code = new List<Instruction>();
			K = new List<StkId>();
			P = new List<LuaProto>();
			Upvalues = new List<UpvalDesc>();
			LineInfo = new List<int>();
			LocVars = new List<LocVar>();
		}

		public int GetFuncLine( int pc )
		{
			return (0 <= pc && pc < LineInfo.Count) ? LineInfo[pc] : 0;
		}
	}
	
	public class LuaUpvalue
	{
		public StkId			V;
		public StkId			Value;

		public LuaUpvalue()
		{
			Value = new StkId();
			Value.V.SetNilValue();

			V = Value;
		}
	}

	public class LuaCsClosureValue
	{
		public CSharpFunctionDelegate 	F;
		public StkId[]					Upvals;

		public LuaCsClosureValue( CSharpFunctionDelegate f )
		{
			F = f;
		}

		public LuaCsClosureValue( CSharpFunctionDelegate f, int numUpvalues )
		{
			F = f;
			Upvals = new StkId[numUpvalues];
			for(int i=0; i<numUpvalues; ++i) {
				var newItem = new StkId();
				Upvals[i] = newItem;
				newItem.SetList(Upvals);
				newItem.SetIndex(i);
			}
		}
	}

	public partial class LuaState
	{
		internal static StkId TheNilValue;

		public static bool O_Str2Decimal( string s, out double result )
		{
			result = 0.0;

			if( s.Contains("n") || s.Contains("N") ) // reject `inf' and `nan'
				return false;

			int pos = 0;
			if( s.Contains("x") || s.Contains("X") )
				result = Utl.StrX2Number( s, ref pos );
			else
				result = Utl.Str2Number( s, ref pos );

			if( pos == 0 )
				return false; // nothing recognized

			while( pos < s.Length && Char.IsWhiteSpace( s[pos] ) ) ++pos;
			return pos == s.Length; // OK if no trailing characters
		}

		private static double O_Arith( LuaOp op, double v1, double v2 )
		{
			switch( op )
			{
				case LuaOp.LUA_OPADD: return v1+v2;
				case LuaOp.LUA_OPSUB: return v1-v2;
				case LuaOp.LUA_OPMUL: return v1*v2;
				case LuaOp.LUA_OPDIV: return v1/v2;
				case LuaOp.LUA_OPMOD: return v1 - Math.Floor(v1/v2)*v2;
				case LuaOp.LUA_OPPOW: return Math.Pow(v1, v2);
				case LuaOp.LUA_OPUNM: return -v1;
				default: throw new System.NotImplementedException();
			}
		}

		private bool IsFalse(ref TValue v)
		{
			if( v.TtIsNil() )
				return true;
				
			if((v.TtIsBoolean() && v.BValue() == false))
				return true;

			return false;
		}

		private bool ToString(ref TValue o)
		{
			if(o.TtIsString()) { return true; }
			return V_ToString(ref o);
		}

		internal LuaLClosureValue GetCurrentLuaFunc(CallInfo ci)
		{
			if(ci.IsLua) {
				return Stack[ci.FuncIndex].V.ClLValue();
			}
			else return null;
		}

		internal int GetCurrentLine(CallInfo ci)
		{
			Utl.Assert(ci.IsLua);
			var cl = Stack[ci.FuncIndex].V.ClLValue();
			return cl.Proto.GetFuncLine(ci.CurrentPc);
		}
	}

}

