using System;
using Cosmos.IL2CPU.X86;
using CPU = Cosmos.Compiler.Assembler.X86;
using Cosmos.Compiler.Assembler.X86;
using Label = Cosmos.Compiler.Assembler.Label;

namespace Cosmos.IL2CPU.X86.IL {
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Beq)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Bge)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Bgt)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Ble)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Blt)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Bne_Un)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Bge_Un)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Bgt_Un)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Ble_Un)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Blt_Un)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Brfalse)]
  [Cosmos.IL2CPU.OpCode(ILOpCode.Code.Brtrue)]
  public class Branch: ILOp {

    public Branch(Cosmos.Compiler.Assembler.Assembler aAsmblr)
      : base(aAsmblr) {
    }

    public override void Execute(MethodInfo aMethod, ILOpCode aOpCode) {
      var xStackContent = Assembler.Stack.Pop();
      var xIsSingleCompare = true;
      switch (aOpCode.OpCode) {
        case ILOpCode.Code.Beq:
        case ILOpCode.Code.Bge:
        case ILOpCode.Code.Bgt:
        case ILOpCode.Code.Bge_Un:
        case ILOpCode.Code.Bgt_Un:
        case ILOpCode.Code.Ble:
        case ILOpCode.Code.Ble_Un:
        case ILOpCode.Code.Bne_Un:
        case ILOpCode.Code.Blt:
        case ILOpCode.Code.Blt_Un:
          Assembler.Stack.Pop();
          xIsSingleCompare = false;
          break;
      }

      if (xStackContent.Size > 8) {
        throw new Exception("StackSize > 8 not supported");
      }

      CPU.ConditionalTestEnum xTestOp;
      // all conditions are inverted here?
      switch (aOpCode.OpCode) {
        case ILOpCode.Code.Beq:
          xTestOp = CPU.ConditionalTestEnum.Zero;
          break;
        case ILOpCode.Code.Bge:
          xTestOp = CPU.ConditionalTestEnum.GreaterThanOrEqualTo;
          break;
        case ILOpCode.Code.Bgt:
          xTestOp = CPU.ConditionalTestEnum.GreaterThan;
          break;
        case ILOpCode.Code.Ble:
          xTestOp = CPU.ConditionalTestEnum.LessThanOrEqualTo;
          break;
        case ILOpCode.Code.Blt:
          xTestOp = CPU.ConditionalTestEnum.LessThan;
          break;
        case ILOpCode.Code.Bne_Un:
          xTestOp = CPU.ConditionalTestEnum.NotEqual;
          break;
        case ILOpCode.Code.Bge_Un:
          xTestOp = CPU.ConditionalTestEnum.AboveOrEqual;
          break;
        case ILOpCode.Code.Bgt_Un:
          xTestOp = CPU.ConditionalTestEnum.Above;
          break;
        case ILOpCode.Code.Ble_Un:
          xTestOp = CPU.ConditionalTestEnum.BelowOrEqual;
          break;
        case ILOpCode.Code.Blt_Un:
          xTestOp = CPU.ConditionalTestEnum.Below;
          break;
        case ILOpCode.Code.Brfalse:
          xTestOp = CPU.ConditionalTestEnum.Zero;
          break;
        case ILOpCode.Code.Brtrue:
          xTestOp = CPU.ConditionalTestEnum.NotZero;
          break;
        default:
          throw new Exception("Unknown OpCode for conditional branch.");
      }
      if (!xIsSingleCompare) {
        if (xStackContent.Size <= 4) {
          new CPU.Pop { DestinationReg = CPU.Registers.EAX };
          new CPU.Pop { DestinationReg = CPU.Registers.EBX };
          new CPU.Compare { DestinationReg = CPU.Registers.EBX, SourceReg = CPU.Registers.EAX };
          new CPU.ConditionalJump { Condition = xTestOp, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
        } else {
		  var xNoJump = GetLabel(aMethod, aOpCode) + "__NoBranch";

			// value 2  EBX:EAX
          new CPU.Pop { DestinationReg = CPU.Registers.EAX };
          new CPU.Pop { DestinationReg = CPU.Registers.EBX };
			// value 1  EDX:ECX
          new CPU.Pop { DestinationReg = CPU.Registers.ECX };
          new CPU.Pop { DestinationReg = CPU.Registers.EDX };
		  switch (xTestOp)
		  {
			  case ConditionalTestEnum.Zero: // Equal
			  case ConditionalTestEnum.NotEqual: // NotZero
				  new CPU.Xor { DestinationReg = CPU.Registers.EAX, SourceReg = CPU.Registers.ECX };
				  new CPU.ConditionalJump { Condition = xTestOp, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.Xor { DestinationReg = CPU.Registers.EBX, SourceReg = CPU.Registers.EDX };
				  new CPU.ConditionalJump { Condition = xTestOp, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  break;
			  case ConditionalTestEnum.GreaterThanOrEqualTo:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.LessThan, DestinationLabel = xNoJump };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.GreaterThan, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Below, DestinationLabel = xNoJump };
				  break;
			  case ConditionalTestEnum.GreaterThan:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.LessThan, DestinationLabel = xNoJump };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.GreaterThan, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.BelowOrEqual, DestinationLabel = xNoJump };
				  break;
			  case ConditionalTestEnum.LessThanOrEqualTo:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.LessThan, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.GreaterThan, DestinationLabel = xNoJump };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.BelowOrEqual, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  break;
			  case ConditionalTestEnum.LessThan:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.LessThan, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.GreaterThan, DestinationLabel = xNoJump };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Below, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  break;
			// from here all unsigned
			  case ConditionalTestEnum.AboveOrEqual:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Above, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Below, DestinationLabel = xNoJump };
				  break;
			  case ConditionalTestEnum.Above:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Above, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.BelowOrEqual, DestinationLabel = xNoJump };
				  break;
			  case ConditionalTestEnum.BelowOrEqual:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Above, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Below, DestinationLabel = xNoJump };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Above, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  break;
			  case ConditionalTestEnum.Below:
				  new CPU.Compare { DestinationReg = CPU.Registers.EDX, SourceReg = CPU.Registers.EBX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Above, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.Below, DestinationLabel = xNoJump };
				  new CPU.Compare { DestinationReg = CPU.Registers.ECX, SourceReg = CPU.Registers.EAX };
				  new CPU.ConditionalJump { Condition = CPU.ConditionalTestEnum.AboveOrEqual, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
				  break;
			  default:
				  throw new Exception("Unknown OpCode for conditional branch in 64-bit.");
		  }
		  new Label(xNoJump);
        }
      } else {
        // todo: improve code clarity
        if (xStackContent.Size > 4) {
          throw new Exception("Simple branches are not supported yet on operands > 4 bytes!");
        }
        new CPU.Pop { DestinationReg = CPU.Registers.EAX };
        if (xTestOp == ConditionalTestEnum.Zero) {
          new CPU.Compare { DestinationReg = CPU.Registers.EAX, SourceValue = 0 };
          new CPU.ConditionalJump { Condition = ConditionalTestEnum.Equal, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
        } else if (xTestOp == ConditionalTestEnum.NotZero) {
          new CPU.Compare { DestinationReg = CPU.Registers.EAX, SourceValue = 0 };
          new CPU.ConditionalJump { Condition = ConditionalTestEnum.NotEqual, DestinationLabel = AppAssemblerNasm.TmpBranchLabel(aMethod, aOpCode) };
        } else {
          throw new NotSupportedException("Situation not supported yet!");
        }
      }
    }
  }
}