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

using NotImplementedException = System.NotImplementedException;

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

	public struct Instruction
	{
		public uint Value;

		public Instruction( uint val )
		{
			Value = val;
		}

		public static explicit operator Instruction( uint val )
		{
			return new Instruction(val);
		}

		public static explicit operator uint( Instruction i )
		{
			return i.Value;
		}

		public override string ToString()
		{
			var op   = GET_OPCODE();
			var a    = GETARG_A();
			var b    = GETARG_B();
			var c    = GETARG_C();
			var ax   = GETARG_Ax();
			var bx   = GETARG_Bx();
			var sbx  = GETARG_sBx();
			var mode = OpCodeInfo.GetMode( op );
			switch( mode.OpMode )
			{
				case OpMode.iABC:
				{
					string ret = string.Format( "{0,-9} {1}"
						, op
						, a );
					if( mode.BMode != OpArgMask.OpArgN )
						ret += " " + (ISK(b) ? MYK(INDEXK(b)) : b);
					if( mode.CMode != OpArgMask.OpArgN )
						ret += " " + (ISK(c) ? MYK(INDEXK(c)) : c);
					return ret;
				}
				case OpMode.iABx:
				{
					string ret = string.Format( "{0,-9} {1}"
						, op
						, a );
					if( mode.BMode == OpArgMask.OpArgK )
						ret += " " + MYK(bx);
					else if( mode.BMode == OpArgMask.OpArgU )
						ret += " " + bx;
					return ret;
				}
				case OpMode.iAsBx:
				{
					return string.Format( "{0,-9} {1} {2}"
						, op
						, a
						, sbx );
				}
				case OpMode.iAx:
				{
					return string.Format( "{0,-9} {1}"
						, op
						, MYK(ax) );
				}
				default:
					throw new System.NotImplementedException();
			}
		}

		public const int SIZE_C = 9;
		public const int SIZE_B = 9;
		public const int SIZE_Bx = (SIZE_C + SIZE_B);
		public const int SIZE_A = 8;
		public const int SIZE_Ax = (SIZE_C + SIZE_B + SIZE_A);

		public const int SIZE_OP = 6;

		public const int POS_OP = 0;
		public const int POS_A = (POS_OP + SIZE_OP);
		public const int POS_C = (POS_A + SIZE_A);
		public const int POS_B = (POS_C + SIZE_C);
		public const int POS_Bx = POS_C;
		public const int POS_Ax = POS_A;

#pragma warning disable 0429
		public const int MAXARG_Bx  = SIZE_Bx<LuaConf.LUAI_BITSINT
									? ((1<<SIZE_Bx)-1)
									: LuaLimits.MAX_INT
									;
		public const int MAXARG_sBx = SIZE_Bx<LuaConf.LUAI_BITSINT
									? (MAXARG_Bx>>1)
									: LuaLimits.MAX_INT
									;
#pragma warning restore 0429

		public const int MAXARG_Ax = ((1<<SIZE_Ax) - 1);

		public const int MAXARG_A = ((1<<SIZE_A) - 1);
		public const int MAXARG_B = ((1<<SIZE_B) - 1);
		public const int MAXARG_C = ((1<<SIZE_C) - 1);

		public const int BITRK = (1 << (SIZE_B - 1));

		public const int MAXINDEXRK = (BITRK - 1);

		public static int RKASK( int x )
		{
			return (x | BITRK );
		}

		public static bool ISK( int x )
		{
			return ((x) & BITRK ) != 0;
		}

		public static int INDEXK( int r )
		{
			return ((int)r & ~BITRK );
		}

		public static int MYK( int x )
		{
			return (-1-x);
		}

		public static uint MASK1( int size, int pos )
		{
			return ((~((~((uint)0)) << size)) << pos);
		}

		public static uint MASK0( int size, int pos )
		{
			return (~MASK1(size, pos));
		}

		public OpCode GET_OPCODE()
		{
			return (OpCode)( (Value >> POS_OP) & MASK1(SIZE_OP, 0) );
		}

		public Instruction SET_OPCODE( OpCode op )
		{
			Value = (Value & MASK0(SIZE_OP, POS_OP)) |
				((((uint)op) << POS_OP) & MASK1(SIZE_OP, POS_OP));
			return this;
		}

		public int GETARG( int pos, int size )
		{
			return (int)( (Value >> pos) & MASK1(size, 0) );
		}

		public Instruction SETARG( int value, int pos, int size )
		{
			Value = ((Value & MASK0(size, pos)) |
				(((uint)value << pos) & MASK1(size, pos)));
			return this;
		}

		public int GETARG_A() { return GETARG( POS_A, SIZE_A ); }
		public Instruction SETARG_A(int value) {
			return SETARG( value, POS_A, SIZE_A );
		}

		public int GETARG_B() { return GETARG( POS_B, SIZE_B ); }
		public Instruction SETARG_B(int value) {
			return SETARG( value, POS_B, SIZE_B );
		}

		public int GETARG_C() { return GETARG( POS_C, SIZE_C ); }
		public Instruction SETARG_C(int value) {
			return SETARG( value, POS_C, SIZE_C );
		}

		public int GETARG_Bx() { return GETARG( POS_Bx, SIZE_Bx ); }
		public Instruction SETARG_Bx(int value) {
			return SETARG( value, POS_Bx, SIZE_Bx );
		}

		public int GETARG_Ax() { return GETARG( POS_Ax, SIZE_Ax ); }
		public Instruction SETARG_Ax(int value) {
			return SETARG( value, POS_Ax, SIZE_Ax );
		}

		public int GETARG_sBx() { return GETARG_Bx() - MAXARG_sBx; }
		public Instruction SETARG_sBx(int value) {
			return SETARG_Bx(value+MAXARG_sBx);
		}

		public static Instruction CreateABC( OpCode op, int a, int b, int c )
		{
			return (Instruction)( (((uint)op) << POS_OP)
				| ((uint)a << POS_A)
				| ((uint)b << POS_B)
				| ((uint)c << POS_C));
		}

		public static Instruction CreateABx( OpCode op, int a, uint bc )
		{
			return (Instruction)( (((uint)op) << POS_OP)
				| ((uint)a  << POS_A)
				| ((uint)bc << POS_Bx));
		}

		public static Instruction CreateAx( OpCode op, int a )
		{
			return (Instruction)( (((uint)op) << POS_OP)
				| ((uint)a  << POS_Ax));
		}
	}

	public static class Coder
	{
		public const int NO_JUMP = -1;
		private const int NO_REG  = ((1<<Instruction.SIZE_A) - 1);

		private static void FreeReg( FuncState fs, int reg )
		{
			if( !Instruction.ISK(reg) && reg >= fs.NumActVar )
			{
				fs.FreeReg--;
				Utl.Assert( reg == fs.FreeReg );
			}
		}

		private static void FreeExp( FuncState fs, ExpDesc e )
		{
			if( e.Kind == ExpKind.VNONRELOC )
			{
				FreeReg( fs, e.Info );
			}
		}

		private static bool IsNumeral( ExpDesc e )
		{
			return e.Kind == ExpKind.VKNUM
				&& e.ExitTrue == NO_JUMP
				&& e.ExitFalse == NO_JUMP;
		}

		private static bool ConstFolding( OpCode op, ExpDesc e1, ExpDesc e2 )
		{
			if( !IsNumeral(e1) || !IsNumeral(e2) )
				return false;

			if( (op == OpCode.OP_DIV || op == OpCode.OP_MOD)
				&& e2.NumberValue == 0.0 )
			{
				return false; // do not attempt to divide by 0
			}

			switch( op )
			{
				case OpCode.OP_ADD:
					e1.NumberValue = e1.NumberValue + e2.NumberValue;
					break;
				case OpCode.OP_SUB:
					e1.NumberValue = e1.NumberValue - e2.NumberValue;
					break;
				case OpCode.OP_MUL:
					e1.NumberValue = e1.NumberValue * e2.NumberValue;
					break;
				case OpCode.OP_DIV:
					e1.NumberValue = e1.NumberValue / e2.NumberValue;
					break;
				case OpCode.OP_MOD:
					var v1 = e1.NumberValue;
					var v2 = e2.NumberValue;
					e1.NumberValue = v1 - Math.Floor(v1/v2) * v2;
					break;
				case OpCode.OP_POW:
					e1.NumberValue = Math.Pow( e1.NumberValue, e2.NumberValue );
					break;
				case OpCode.OP_UNM:
					e1.NumberValue = -e1.NumberValue;
					break;
				default:
					throw new Exception("ConstFolding unknown op" + op);
			}

			return true;
		}

		public static void FixLine( FuncState fs, int line )
		{
			fs.Proto.LineInfo[ fs.Pc-1 ] = line;
		}

		private static void CodeArith( FuncState fs, OpCode op,
			ExpDesc e1, ExpDesc e2, int line )
		{
			if( ConstFolding( op, e1, e2 ) )
				return;

			int o2 = ( op != OpCode.OP_UNM && op != OpCode.OP_LEN )
				? Exp2RK( fs, e2 ) : 0;
			int o1 = Exp2RK( fs, e1 );
			if( o1 > o2 )
			{
				FreeExp( fs, e1 );
				FreeExp( fs, e2 );
			}
			else
			{
				FreeExp( fs, e2 );
				FreeExp( fs, e1 );
			}
			e1.Info = CodeABC( fs, op, 0, o1, o2 );
			e1.Kind = ExpKind.VRELOCABLE;
			FixLine( fs, line );
		}

		public static bool TestTMode( OpCode op )
		{
			return OpCodeInfo.GetMode( op ).TMode;
		}

		public static bool TestAMode( OpCode op )
		{
			return OpCodeInfo.GetMode( op ).AMode;
		}

		private static void FixJump( FuncState fs, int pc, int dest )
		{
			Instruction jmp = fs.Proto.Code[pc];
			int offset = dest - (pc + 1);
			Utl.Assert( dest != NO_JUMP );
			if( Math.Abs(offset) > Instruction.MAXARG_sBx )
				fs.Lexer.SyntaxError("control structure too long");
			jmp.SETARG_sBx( offset );
			fs.Proto.Code[pc] = jmp;
		}

		// returns current `pc' and mark it as a jump target
		// (to avoid wrong optimizations with consecutive
		// instructions not in the same basic block)
		public static int GetLabel( FuncState fs )
		{
			fs.LastTarget = fs.Pc;
			return fs.Pc;
		}

		private static int GetJump( FuncState fs, int pc )
		{
			int offset = fs.Proto.Code[pc].GETARG_sBx();
			if( offset == NO_JUMP ) // point to itself represents end of list
				return NO_JUMP; // end of list
			else
				return (pc+1) + offset; // turn offset into absolute position
		}

		private static InstructionPtr GetJumpControl( FuncState fs, int pc )
		{
			InstructionPtr pi = new InstructionPtr( fs.Proto.Code, pc );
			if( pc >= 1 && TestTMode( (pi-1).Value.GET_OPCODE() ))
				return (pi-1);
			else
				return pi;
		}

		// check whether list has any jump that do not produce a value
		// (or produce an inverted value)
		private static bool NeedValue( FuncState fs, int list )
		{
			for( ; list != NO_JUMP; list = GetJump( fs, list ) )
			{
				Instruction i = GetJumpControl( fs, list ).Value;
				if( i.GET_OPCODE() != OpCode.OP_TESTSET )
					return true;
			}
			return false;
		}

		private static bool PatchTestReg( FuncState fs, int node, int reg )
		{
			InstructionPtr pi = GetJumpControl( fs, node );
			if( pi.Value.GET_OPCODE() != OpCode.OP_TESTSET )
				return false; // cannot patch other instructions

			if( reg != NO_REG && reg != pi.Value.GETARG_B() )
				pi.Value = pi.Value.SETARG_A( reg );
			else
				pi.Value = Instruction.CreateABC( OpCode.OP_TEST,
					pi.Value.GETARG_B(), 0, pi.Value.GETARG_C() );

			return true;
		}

		private static void RemoveValues( FuncState fs, int list )
		{
			for(; list != NO_JUMP; list = GetJump( fs, list ) )
				PatchTestReg( fs, list, NO_REG );
		}

		private static void PatchListAux( FuncState fs, int list, int vtarget,
			int reg, int dtarget )
		{
			while( list != NO_JUMP )
			{
				int next = GetJump( fs, list );
				if( PatchTestReg( fs, list, reg ) )
					FixJump( fs, list, vtarget );
				else
					FixJump( fs, list, dtarget ); // jump to default target
				list = next;
			}
		}

		private static void DischargeJpc( FuncState fs )
		{
			PatchListAux( fs, fs.Jpc, fs.Pc, NO_REG, fs.Pc );
			fs.Jpc = NO_JUMP;
		}

		private static void InvertJump( FuncState fs, ExpDesc e )
		{
			InstructionPtr pc = GetJumpControl( fs, e.Info );
			Utl.Assert( TestTMode( pc.Value.GET_OPCODE() )
				&& pc.Value.GET_OPCODE() != OpCode.OP_TESTSET
				&& pc.Value.GET_OPCODE() != OpCode.OP_TEST );
			pc.Value = pc.Value.SETARG_A( pc.Value.GETARG_A() == 0 ? 1 : 0 );
		}

		private static int JumpOnCond( FuncState fs, ExpDesc e, bool cond )
		{
			if( e.Kind == ExpKind.VRELOCABLE )
			{
				Instruction ie = fs.GetCode( e ).Value;
				if( ie.GET_OPCODE() == OpCode.OP_NOT )
				{
					fs.Pc--; // remove previous OP_NOT
					return CondJump( fs, OpCode.OP_TEST, ie.GETARG_B(), 0,
						(cond ? 0 : 1) );
				}
				// else go through
			}
			Discharge2AnyReg( fs, e );
			FreeExp( fs, e );
			return CondJump( fs, OpCode.OP_TESTSET, NO_REG, e.Info,
				(cond ? 1 : 0) );
		}

		public static void GoIfTrue( FuncState fs, ExpDesc e )
		{
			int pc; // pc of last jump
			DischargeVars( fs, e );
			switch( e.Kind )
			{
				case ExpKind.VJMP:
					InvertJump( fs, e );
					pc = e.Info;
					break;

				case ExpKind.VK:
				case ExpKind.VKNUM:
				case ExpKind.VTRUE:
					pc = NO_JUMP;
					break;

				default:
					pc = JumpOnCond( fs, e, false );
					break;
			}

			// insert last jump in `f' list
			e.ExitFalse = Concat( fs, e.ExitFalse, pc );
			PatchToHere( fs, e.ExitTrue );
			e.ExitTrue = NO_JUMP;
		}

		public static void GoIfFalse( FuncState fs, ExpDesc e )
		{
			int pc; // pc of last jump
			DischargeVars( fs, e );
			switch( e.Kind )
			{
				case ExpKind.VJMP:
					pc = e.Info;
					break;

				case ExpKind.VNIL:
				case ExpKind.VFALSE:
					pc = NO_JUMP;
					break;

				default:
					pc = JumpOnCond( fs, e, true );
					break;
			}

			// insert last jump in `t' list
			e.ExitTrue = Concat( fs, e.ExitTrue, pc );
			PatchToHere( fs, e.ExitFalse );
			e.ExitFalse = NO_JUMP;
		}

		private static void CodeNot( FuncState fs, ExpDesc e )
		{
			DischargeVars( fs, e );
			switch( e.Kind )
			{
				case ExpKind.VNIL:
				case ExpKind.VFALSE:
					e.Kind = ExpKind.VTRUE;
					break;

				case ExpKind.VK:
				case ExpKind.VKNUM:
				case ExpKind.VTRUE:
					e.Kind = ExpKind.VFALSE;
					break;

				case ExpKind.VJMP:
					InvertJump( fs, e );
					break;

				case ExpKind.VRELOCABLE:
				case ExpKind.VNONRELOC:
					Discharge2AnyReg( fs, e );
					FreeExp( fs, e );
					e.Info = CodeABC( fs, OpCode.OP_NOT, 0, e.Info, 0 );
					e.Kind = ExpKind.VRELOCABLE;
					break;

				default:
					throw new Exception("CodeNot unknown e.Kind:" + e.Kind);
			}

			// interchange true and false lists
			{ int temp = e.ExitFalse; e.ExitFalse = e.ExitTrue; e.ExitTrue = temp; }

			RemoveValues( fs, e.ExitFalse );
			RemoveValues( fs, e.ExitTrue  );
		}

		private static void CodeComp( FuncState fs, OpCode op, int cond,
			ExpDesc e1, ExpDesc e2 )
		{
			int o1 = Exp2RK( fs, e1 );
			int o2 = Exp2RK( fs, e2 );
			FreeExp( fs, e2 );
			FreeExp( fs, e1 );

			// exchange args to replace by `<' or `<='
			if( cond == 0 && op != OpCode.OP_EQ ) {
				int temp;
				temp = o1; o1 = o2; o2 = temp; // o1 <==> o2
				cond = 1;
			}
			e1.Info = CondJump( fs, op, cond, o1, o2 );
			e1.Kind = ExpKind.VJMP;
		}

		public static void Prefix( FuncState fs, UnOpr op, ExpDesc e, int line )
		{
			ExpDesc e2 = new ExpDesc();
			e2.ExitTrue = NO_JUMP;
			e2.ExitFalse = NO_JUMP;
			e2.Kind = ExpKind.VKNUM;
			e2.NumberValue = 0.0;

			switch( op )
			{
				case UnOpr.MINUS: {
					if( IsNumeral( e ) ) // minus constant?
					{
						e.NumberValue = -e.NumberValue;
					}
					else
					{
						Exp2AnyReg( fs, e );
						CodeArith( fs, OpCode.OP_UNM, e, e2, line );
					}
				} break;

				case UnOpr.NOT: {
					CodeNot( fs, e );
				} break;

				case UnOpr.LEN: {
					Exp2AnyReg( fs, e ); // cannot operate on constants
					CodeArith( fs, OpCode.OP_LEN, e, e2, line );
				} break;

				default:
					throw new Exception("[Coder]Prefix Unknown UnOpr:" + op);
			}
		}

		public static void Infix( FuncState fs, BinOpr op, ExpDesc e )
		{
			switch( op )
			{
				case BinOpr.AND: {
					GoIfTrue( fs, e );
				} break;

				case BinOpr.OR: {
					GoIfFalse( fs, e );
				} break;

				case BinOpr.CONCAT: {
					Exp2NextReg( fs, e ); // operand must be on the `stack'
				} break;

				case BinOpr.ADD:
				case BinOpr.SUB:
				case BinOpr.MUL:
				case BinOpr.DIV:
				case BinOpr.MOD:
				case BinOpr.POW: {
					if( !IsNumeral(e) )
						Exp2RK( fs, e );
				} break;

				default: {
					Exp2RK( fs, e );
				} break;
			}
		}

		public static void Posfix( FuncState fs, BinOpr op,
			ExpDesc e1, ExpDesc e2, int line )
		{
			switch( op )
			{
				case BinOpr.AND: {
					Utl.Assert( e1.ExitTrue == NO_JUMP );
					DischargeVars( fs, e2 );
					e2.ExitFalse = Concat( fs, e2.ExitFalse, e1.ExitFalse );
					e1.CopyFrom( e2 );
					break;
				}
				case BinOpr.OR: {
					Utl.Assert( e1.ExitFalse == NO_JUMP );
					DischargeVars( fs, e2 );
					e2.ExitTrue = Concat( fs, e2.ExitTrue, e1.ExitTrue );
					e1.CopyFrom( e2 );
					break;
				}
				case BinOpr.CONCAT: {
					Exp2Val( fs, e2 );
					var pe2 = fs.GetCode( e2 );
					if( e2.Kind == ExpKind.VRELOCABLE &&
						pe2.Value.GET_OPCODE() == OpCode.OP_CONCAT )
					{
						Utl.Assert( e1.Info == pe2.Value.GETARG_B()-1 );
						FreeExp( fs, e1 );
						pe2.Value = pe2.Value.SETARG_B( e1.Info );
						e1.Kind = ExpKind.VRELOCABLE;
						e1.Info = e2.Info;
					}
					else
					{
						// operand must be on the `stack'
						Exp2NextReg( fs, e2 );
						CodeArith( fs, OpCode.OP_CONCAT, e1, e2, line );
					}
					break;
				}
				case BinOpr.ADD: {
					CodeArith( fs, OpCode.OP_ADD, e1, e2, line);
					break;
				}
				case BinOpr.SUB: {
					CodeArith( fs, OpCode.OP_SUB, e1, e2, line);
					break;
				}
				case BinOpr.MUL: {
					CodeArith( fs, OpCode.OP_MUL, e1, e2, line);
					break;
				}
				case BinOpr.DIV: {
					CodeArith( fs, OpCode.OP_DIV, e1, e2, line);
					break;
				}
				case BinOpr.MOD: {
					CodeArith( fs, OpCode.OP_MOD, e1, e2, line);
					break;
				}
				case BinOpr.POW: {
					CodeArith( fs, OpCode.OP_POW, e1, e2, line);
					break;
				}
				case BinOpr.EQ: {
					CodeComp( fs, OpCode.OP_EQ, 1, e1, e2 );
					break;
				}
				case BinOpr.LT: {
					CodeComp( fs, OpCode.OP_LT, 1, e1, e2 );
					break;
				}
				case BinOpr.LE: {
					CodeComp( fs, OpCode.OP_LE, 1, e1, e2 );
					break;
				}
				case BinOpr.NE: {
					CodeComp( fs, OpCode.OP_EQ, 0, e1, e2 );
					break;
				}
				case BinOpr.GT: {
					CodeComp( fs, OpCode.OP_LT, 0, e1, e2 );
					break;
				}
				case BinOpr.GE: {
					CodeComp( fs, OpCode.OP_LE, 0, e1, e2 );
					break;
				}
				default: Utl.Assert(false); break;
			}
		}

		public static int Jump( FuncState fs )
		{
			int jpc = fs.Jpc; // save list of jumps to here
			fs.Jpc = NO_JUMP;
			int j = CodeAsBx( fs, OpCode.OP_JMP, 0, NO_JUMP );
			j = Concat( fs, j, jpc );
			return j;
		}

		public static void JumpTo( FuncState fs, int target )
		{
			PatchList( fs, Jump(fs), target );
		}

		public static void Ret( FuncState fs, int first, int nret )
		{
			CodeABC( fs, OpCode.OP_RETURN, first, nret+1, 0 );
		}

		private static int CondJump( FuncState fs, OpCode op, int a, int b, int c )
		{
			CodeABC( fs, op, a, b, c );
			return Jump( fs );
		}

		public static void PatchList( FuncState fs, int list, int target )
		{
			if( target == fs.Pc )
				PatchToHere( fs, list );
			else
			{
				Utl.Assert( target < fs.Pc );
				PatchListAux( fs, list, target, NO_REG, target );
			}
		}

		public static void PatchClose( FuncState fs, int list, int level )
		{
			level++; // argument is +1 to reserve 0 as non-op
			while( list != NO_JUMP )
			{
				int next = GetJump( fs, list );
				var pi = new InstructionPtr( fs.Proto.Code, list );;
				Utl.Assert( pi.Value.GET_OPCODE() == OpCode.OP_JMP &&
							( pi.Value.GETARG_A() == 0 ||
							  pi.Value.GETARG_A() >= level ) );
				pi.Value = pi.Value.SETARG_A( level );
				list = next;
			}
		}

		public static void PatchToHere( FuncState fs, int list )
		{
			GetLabel( fs );
			fs.Jpc = Concat( fs, fs.Jpc, list );
		}

		public static int Concat( FuncState fs, int l1, int l2 )
		{
			if( l2 == NO_JUMP )
				return l1;
			else if( l1 == NO_JUMP )
				return l2;
			else
			{
				int list = l1;
				int next = GetJump( fs, list );

				// find last element
				while( next != NO_JUMP )
				{
					list = next;
					next = GetJump( fs, list );
				}
				FixJump( fs, list, l2 );
				return l1;
			}
		}

		public static int StringK( FuncState fs, string s )
		{
			var o = new TValue();
            o.SetSValue(s);
            return AddK( fs, ref o, ref o );
		}

		public static int NumberK( FuncState fs, double r )
		{
			var o = new TValue();
			o.SetNValue(r);
            return AddK( fs, ref o, ref o );
		}

		private static int BoolK( FuncState fs, bool b )
		{
			var o = new TValue();
			o.SetBValue(b);
            return AddK( fs, ref o, ref o );
		}

		private static int NilK( FuncState fs )
		{
			// // cannot use nil as key;
			// // instead use table itself to represent nil
			// var k = fs.H;
			// var o = new LuaNil();
			// return AddK( fs, k, o );

			var o = new TValue();
			o.SetNilValue();
            return AddK( fs, ref o, ref o );
		}

        public static int AddK(FuncState fs, ref TValue key, ref TValue v)
        {
            int idx;
            int keyHash = key.GetHashCode();

            if (fs.H.TryGetValue(keyHash, out idx))
                return idx;

            idx = fs.Proto.K.Count;
            fs.H.Add(keyHash, idx);

            var newItem = new StkId();
            newItem.V.SetObj(ref v);
            fs.Proto.K.Add(newItem);
            return idx;
        }

        public static void Indexed( FuncState fs, ExpDesc t, ExpDesc k )
		{
			t.Ind.T = t.Info;
			t.Ind.Idx = Exp2RK( fs, k );
			t.Ind.Vt = (t.Kind == ExpKind.VUPVAL) ? ExpKind.VUPVAL
												  : ExpKind.VLOCAL; // FIXME
			t.Kind = ExpKind.VINDEXED;
		}

		private static bool HasJumps( ExpDesc e )
		{
			return e.ExitTrue != e.ExitFalse;
		}

		private static int CodeLabel( FuncState fs, int a, int b, int jump )
		{
			GetLabel( fs ); // those instructions may be jump targets
			return CodeABC( fs, OpCode.OP_LOADBOOL, a, b, jump );
		}

		private static void Discharge2Reg( FuncState fs, ExpDesc e, int reg )
		{
			DischargeVars( fs, e );
			switch( e.Kind )
			{
				case ExpKind.VNIL: {
					CodeNil( fs, reg, 1 );
					break;
				}
				case ExpKind.VFALSE:
				case ExpKind.VTRUE: {
					CodeABC( fs, OpCode.OP_LOADBOOL, reg,
						(e.Kind == ExpKind.VTRUE ? 1 : 0), 0 );
					break;
				}
				case ExpKind.VK: {
					CodeK( fs, reg, e.Info );
					break;
				}
				case ExpKind.VKNUM: {
					CodeK( fs, reg, NumberK( fs, e.NumberValue ) );
					break;
				}
				case ExpKind.VRELOCABLE: {
					InstructionPtr pi = fs.GetCode(e);
					pi.Value = pi.Value.SETARG_A(reg);
					break;
				}
				case ExpKind.VNONRELOC: {
					if( reg != e.Info )
						CodeABC( fs, OpCode.OP_MOVE, reg, e.Info, 0 );
					break;
				}
				default: {
					Utl.Assert( e.Kind == ExpKind.VVOID || e.Kind == ExpKind.VJMP );
					return; // nothing to do...
				}
			}
			e.Info = reg;
			e.Kind = ExpKind.VNONRELOC;
		}

		public static void CheckStack( FuncState fs, int n )
		{
			int newStack = fs.FreeReg + n;
			if( newStack > fs.Proto.MaxStackSize )
			{
				if( newStack >= LuaLimits.MAXSTACK )
				{
					fs.Lexer.SyntaxError("function or expression too complex");
				}
				fs.Proto.MaxStackSize = (byte)newStack;
			}
		}

		public static void ReserveRegs( FuncState fs, int n )
		{
			CheckStack( fs, n );
			fs.FreeReg += n;
		}

		private static void Discharge2AnyReg( FuncState fs, ExpDesc e )
		{
			if( e.Kind != ExpKind.VNONRELOC )
			{
				ReserveRegs( fs, 1 );
				Discharge2Reg( fs, e, fs.FreeReg-1 );
			}
		}

		private static void Exp2Reg( FuncState fs, ExpDesc e, int reg )
		{
			Discharge2Reg( fs, e, reg );
			if( e.Kind == ExpKind.VJMP )
			{
				e.ExitTrue = Concat( fs, e.ExitTrue, e.Info );
			}

			if( HasJumps(e) )
			{
				int p_f = NO_JUMP;
				int p_t = NO_JUMP;
				if( NeedValue( fs, e.ExitTrue ) || NeedValue( fs, e.ExitFalse ) )
				{
					int fj = (e.Kind == ExpKind.VJMP) ? NO_JUMP : Jump( fs );
					p_f = CodeLabel( fs, reg, 0, 1 );
					p_t = CodeLabel( fs, reg, 1, 0 );
					PatchToHere( fs, fj );
				}

				// position after whole expression
				int final = GetLabel( fs );
				PatchListAux( fs, e.ExitFalse, final, reg, p_f );
				PatchListAux( fs, e.ExitTrue,  final, reg, p_t );
			}

			e.ExitFalse = NO_JUMP;
			e.ExitTrue  = NO_JUMP;
			e.Info = reg;
			e.Kind = ExpKind.VNONRELOC;
		}

		public static void Exp2NextReg( FuncState fs, ExpDesc e )
		{
			DischargeVars( fs, e );
			FreeExp( fs, e );
			ReserveRegs( fs, 1 );
			Exp2Reg( fs, e, fs.FreeReg-1 );
		}

		public static void Exp2Val( FuncState fs, ExpDesc e )
		{
			if( HasJumps(e) )
				Exp2AnyReg( fs, e );
			else
				DischargeVars( fs, e );
		}

		public static int Exp2RK( FuncState fs, ExpDesc e )
		{
			Exp2Val( fs, e );
			switch( e.Kind )
			{
				case ExpKind.VTRUE:
				case ExpKind.VFALSE:
				case ExpKind.VNIL: {
					// constant fits in RK operand?
					if( fs.Proto.K.Count <= Instruction.MAXINDEXRK )
					{
						e.Info = (e.Kind == ExpKind.VNIL) ? NilK(fs)
							: BoolK( fs, (e.Kind == ExpKind.VTRUE ) );
						e.Kind = ExpKind.VK;
						return Instruction.RKASK( e.Info );
					}
					else break;
				}
				case ExpKind.VKNUM:
				case ExpKind.VK:
				{
					if( e.Kind == ExpKind.VKNUM )
					{
						e.Info = NumberK( fs, e.NumberValue );
						e.Kind = ExpKind.VK;
					}

					if( e.Info <= Instruction.MAXINDEXRK )
						return Instruction.RKASK( e.Info );
					else break;
				}

				default: break;
			}

			return Exp2AnyReg( fs, e );
		}

		public static int Exp2AnyReg( FuncState fs, ExpDesc e )
		{
			DischargeVars( fs, e );
			if( e.Kind == ExpKind.VNONRELOC )
			{
				// exp is already in a register
				if( ! HasJumps( e ) )
					return e.Info;

				// reg. is not a local?
				if( e.Info >= fs.NumActVar )
				{
					Exp2Reg( fs, e, e.Info );
					return e.Info;
				}
			}
			Exp2NextReg( fs, e ); // default
			return e.Info;
		}

		public static void Exp2AnyRegUp( FuncState fs, ExpDesc e )
		{
			if( e.Kind != ExpKind.VUPVAL || HasJumps( e ) )
			{
				Exp2AnyReg( fs, e );
			}
		}

		public static void DischargeVars( FuncState fs, ExpDesc e )
		{
			switch( e.Kind )
			{
				case ExpKind.VLOCAL:
					e.Kind = ExpKind.VNONRELOC;
					break;

				case ExpKind.VUPVAL:
					e.Info = CodeABC( fs, OpCode.OP_GETUPVAL, 0, e.Info, 0 );
					e.Kind = ExpKind.VRELOCABLE;
					break;

				case ExpKind.VINDEXED:
					OpCode op = OpCode.OP_GETTABUP;
					FreeReg( fs, e.Ind.Idx );
					if( e.Ind.Vt == ExpKind.VLOCAL )
					{
						FreeReg( fs, e.Ind.T );
						op = OpCode.OP_GETTABLE;
					}
					e.Info = CodeABC( fs, op, 0, e.Ind.T, e.Ind.Idx );
					e.Kind = ExpKind.VRELOCABLE;
					break;

				case ExpKind.VVARARG:
				case ExpKind.VCALL:
					SetOneRet( fs, e );
					break;

				default: break;
			}
		}

		public static void SetReturns( FuncState fs, ExpDesc e, int nResults )
		{
			if( e.Kind == ExpKind.VCALL ) { // expression is an open function call?
				var pi = fs.GetCode(e);
				pi.Value = pi.Value.SETARG_C( nResults+1 );
			}
			else if( e.Kind == ExpKind.VVARARG ) {
				var pi = fs.GetCode(e);
				pi.Value = pi.Value.SETARG_B( nResults+1 ).SETARG_A( fs.FreeReg );
				ReserveRegs( fs, 1 );
			}
		}

		public static void SetMultiRet( FuncState fs, ExpDesc e )
		{
			SetReturns( fs, e, LuaDef.LUA_MULTRET );
		}

		public static void SetOneRet( FuncState fs, ExpDesc e )
		{
			// expression is an open function call?
			if( e.Kind == ExpKind.VCALL )
			{
				e.Kind = ExpKind.VNONRELOC;
				e.Info = ( fs.GetCode( e ) ).Value.GETARG_A();
			}
			else if( e.Kind == ExpKind.VVARARG )
			{
				var pi = fs.GetCode( e );
				pi.Value = pi.Value.SETARG_B( 2 );
				e.Kind = ExpKind.VRELOCABLE; // can relocate its simple result
			}
		}

		public static void StoreVar( FuncState fs, ExpDesc v, ExpDesc e )
		{
			switch( v.Kind )
			{
				case ExpKind.VLOCAL: {
					FreeExp( fs, e );
					Exp2Reg( fs, e, v.Info );
					break;
				}

				case ExpKind.VUPVAL: {
					int c = Exp2AnyReg( fs, e );
					CodeABC( fs, OpCode.OP_SETUPVAL, c, v.Info, 0 );
					break;
				}

				case ExpKind.VINDEXED: {
					OpCode op = (v.Ind.Vt == ExpKind.VLOCAL)
						? OpCode.OP_SETTABLE
						: OpCode.OP_SETTABUP;
					int c = Exp2RK( fs, e );
					CodeABC( fs, op, v.Ind.T, v.Ind.Idx, c );
					break;
				}

				default:
				{
					throw new NotImplementedException("invalid var kind to store");
				}
			}
			FreeExp( fs, e );
		}

		public static void Self( FuncState fs, ExpDesc e, ExpDesc key )
		{
			Exp2AnyReg( fs, e );
			int ereg = e.Info; // register where `e' is placed
			FreeExp( fs, e );
			e.Info = fs.FreeReg; // base register for op_self
			e.Kind = ExpKind.VNONRELOC;
			ReserveRegs( fs, 2 );
			CodeABC( fs, OpCode.OP_SELF, e.Info, ereg, Coder.Exp2RK(fs, key) );
			FreeExp( fs, key );
		}

		public static void SetList( FuncState fs, int t, int nelems, int tostore )
		{
			int c = (nelems - 1) / LuaDef.LFIELDS_PER_FLUSH + 1;
			int b = (tostore == LuaDef.LUA_MULTRET) ? 0 : tostore;
			Utl.Assert( tostore != 0 );

			if( c <= Instruction.MAXARG_C )
			{
				CodeABC( fs, OpCode.OP_SETLIST, t, b, c );
			}
			else if( c <= Instruction.MAXARG_Ax )
			{
				CodeABC( fs, OpCode.OP_SETLIST, t, b, 0 );
				CodeExtraArg( fs, c );
			}
			else
			{
				fs.Lexer.SyntaxError("constructor too long");
			}

			// free registers with list values
			fs.FreeReg = t + 1;
		}

		public static void CodeNil( FuncState fs, int from, int n )
		{
			int l = from + n - 1; // last register to set nil
			if( fs.Pc > fs.LastTarget ) // no jumps to current position?
			{
				var previous = new InstructionPtr( fs.Proto.Code, fs.Pc-1 );
				if( previous.Value.GET_OPCODE() == OpCode.OP_LOADNIL )
				{
					int pfrom = previous.Value.GETARG_A();
					int pl = pfrom + previous.Value.GETARG_B();

					// can connect both?
					if( (pfrom <= from && from <= pl + 1) ||
						(from <= pfrom && pfrom <= l + 1))
					{
						if( pfrom < from ) from = pfrom; // from=min(from,pfrom)
						if( pl > l ) l = pl; // l=max(l,pl)
						previous.Value = previous.Value.SETARG_A( from );
						previous.Value = previous.Value.SETARG_B( l - from );
						return;
					}
				}
				// else go through
			}

			// else no optimization
			CodeABC( fs, OpCode.OP_LOADNIL, from, n-1, 0 );
		}

		private static int CodeExtraArg( FuncState fs, int a )
		{
			Utl.Assert( a <= Instruction.MAXARG_Ax );
			return Code( fs, Instruction.CreateAx( OpCode.OP_EXTRAARG, a ) );
		}

		public static int CodeK( FuncState fs, int reg, int k )
		{
			if( k <= Instruction.MAXARG_Bx )
				return CodeABx( fs, OpCode.OP_LOADK, reg, (uint)k );
			else
			{
				int p = CodeABx( fs, OpCode.OP_LOADKX, reg, 0 );
				CodeExtraArg( fs, k );
				return p;
			}
		}

		public static int CodeAsBx( FuncState fs, OpCode op, int a, int sBx )
		{
			return CodeABx( fs, op, a, ((uint)sBx)+Instruction.MAXARG_sBx);
		}

		public static int CodeABx( FuncState fs, OpCode op, int a, uint bc )
		{
			var mode = OpCodeInfo.GetMode(op);
			Utl.Assert( mode.OpMode == OpMode.iABx
					 || mode.OpMode == OpMode.iAsBx );
			Utl.Assert( mode.CMode == OpArgMask.OpArgN );
			Utl.Assert( a < Instruction.MAXARG_A & bc <= Instruction.MAXARG_Bx );
			return Code( fs, Instruction.CreateABx( op, a, bc ) );
		}

		public static int CodeABC( FuncState fs, OpCode op, int a, int b, int c )
		{
			return Code( fs, Instruction.CreateABC( op, a, b, c ) );
		}

		public static int Code( FuncState fs, Instruction i )
		{
			DischargeJpc( fs ); // `pc' will change

			while( fs.Proto.Code.Count <= fs.Pc )
			{
				fs.Proto.Code.Add( new Instruction(LuaLimits.MAX_INT) );
			}
			fs.Proto.Code[ fs.Pc ] = i;

			while( fs.Proto.LineInfo.Count <= fs.Pc )
			{
				fs.Proto.LineInfo.Add( LuaLimits.MAX_INT );
			}
			fs.Proto.LineInfo[ fs.Pc ] = fs.Lexer.LastLine;

			return fs.Pc++;
		}
	}

}

