using System;
using System.Linq;
using System.Reflection;
using Indy.IL2CPU.Assembler;
using Indy.IL2CPU.Assembler.X86;
using CPU = Indy.IL2CPU.Assembler;
using CPUx86 = Indy.IL2CPU.Assembler.X86;

namespace Indy.IL2CPU.IL.X86 {
	public abstract class Op: IL.Op {
		private bool mNeedsExceptionPush;
		private Type mCatchType;
		private string mNextInstructionLabel;
		private bool mNeedsTypeCheck = false;

		/// <summary>
		/// Emits code for checking a given address for null, and emits a "throw new NullRefException();" if so.
		/// </summary>
		/// <param name="aAssembler"></param>
		/// <param name="aAddress"></param>
		public static void EmitCompareWithNull(Assembler.Assembler aAssembler, MethodInformation aMethodInfo, string aAddress, string aCurrentLabel, string aNextLabel, Action aEmitCleanupMethod, int aCurrentILOffset) {
			new CPUx86.Compare("dword " + aAddress, "0");
			new CPUx86.JumpIfNotEqual(aNextLabel);
			Type xNullRefExcType = typeof(NullReferenceException);
			Newobj.Assemble(aAssembler, xNullRefExcType.GetConstructor(new Type[0]), Engine.RegisterType(xNullRefExcType), aCurrentLabel, aMethodInfo, aCurrentILOffset);
			aAssembler.StackContents.Pop();
			new CPUx86.Move("[" + DataMember.GetStaticFieldName(CPU.Assembler.CurrentExceptionRef) + "]", "eax");
			Engine.QueueMethod(CPU.Assembler.CurrentExceptionOccurredRef);
			new CPUx86.Call(Label.GenerateLabelName(CPU.Assembler.CurrentExceptionOccurredRef));
			new CPUx86.Move("ecx", "3");
			aEmitCleanupMethod();
			Call.EmitExceptionLogic(aAssembler, aCurrentILOffset, aMethodInfo, aNextLabel, false, null);
		}

		public Op(ILReader aReader, MethodInformation aMethodInfo)
			: base(aReader, aMethodInfo) {
			if (aMethodInfo != null && aMethodInfo.CurrentHandler != null) {
				mNeedsExceptionPush =
					((aMethodInfo.CurrentHandler.HandlerOffset > 0 && aMethodInfo.CurrentHandler.HandlerOffset == aReader.Position) ||
					((aMethodInfo.CurrentHandler.Flags & ExceptionHandlingClauseOptions.Filter) > 0 && aMethodInfo.CurrentHandler.FilterOffset > 0 && aMethodInfo.CurrentHandler.FilterOffset == aReader.Position))
					&& (aMethodInfo.CurrentHandler.Flags == ExceptionHandlingClauseOptions.Clause);
				// todo: add support for exception clear again
				//mNeedsExceptionClear = ((aMethodInfo.CurrentHandler.HandlerOffset + aMethodInfo.CurrentHandler.HandlerLength) == (aReader.Offset + 1)) || 
				//    ((aMethodInfo.CurrentHandler.FilterOffset+aMethodInfo.CurrentHandler.Filterle == (aReader.Offset + 1));
				if (mNeedsExceptionPush && aMethodInfo.CurrentHandler.CatchType != null) {
					mCatchType = aMethodInfo.CurrentHandler.CatchType;
				}
			}
			if (mCatchType != null && mCatchType.FullName != "System.Exception") {
				var xHandler = (from item in aMethodInfo.Method.GetMethodBody().ExceptionHandlingClauses
								where item.TryOffset == aMethodInfo.CurrentHandler.TryOffset
								&& item.TryLength == aMethodInfo.CurrentHandler.TryLength
								&& item.HandlerOffset == aMethodInfo.CurrentHandler.HandlerOffset
								&& item.Flags == ExceptionHandlingClauseOptions.Clause
								select item).FirstOrDefault();
				if (xHandler != null) {
					mNextInstructionLabel = GetInstructionLabel(xHandler.HandlerOffset);
				} else {
					// Here we need to detect where to leave to when this catch clause doesnt' handle a specific exception, and is the last one
					throw new NotImplementedException("TODO: Implement exiting here!");
				}
			} else {
				mCatchType = null;
			}
			if (mCatchType != null && aMethodInfo != null && aMethodInfo.CurrentHandler != null && aMethodInfo.CurrentHandler.HandlerOffset > 0) {
				mNeedsTypeCheck = aMethodInfo.CurrentHandler.HandlerOffset == aReader.NextPosition;
			}
		}

		public static void Ldarg(Assembler.Assembler aAssembler, MethodInformation.Argument aArg) {
			Ldarg(aAssembler, aArg, true);
		}

		public static void Ldarg(Assembler.Assembler aAssembler, MethodInformation.Argument aArg, bool aAddGCCode) {
			foreach (string xAddress in aArg.VirtualAddresses.Reverse()) {
				new Move(CPUx86.Registers.EAX, "[" + xAddress + "]");
				new Push(CPUx86.Registers.EAX);
			}
			aAssembler.StackContents.Push(new StackContent(aArg.Size, aArg.ArgumentType));
			if (aAddGCCode && aArg.IsReferenceType) {
				new CPUx86.Push(CPUx86.Registers.EAX);
				Engine.QueueMethod(GCImplementationRefs.IncRefCountRef);
				new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.IncRefCountRef));
			}
		}

		public static void Ldflda(Assembler.Assembler aAssembler, TypeInformation aType, TypeInformation.Field aField) {
			Ldflda(aAssembler, aType, aField, true);
		}
		public static void Ldflda(Assembler.Assembler aAssembler, TypeInformation aType, TypeInformation.Field aField, bool aDerefExternalAddress) {
			int aExtraOffset = 0;
			if (aType.NeedsGC) {
				aExtraOffset = 12;
			}
			new Popd(CPUx86.Registers.EAX);

			new CPUx86.Add(CPUx86.Registers.EAX, "0x" + (aField.Offset + aExtraOffset).ToString("X"));
			aAssembler.StackContents.Pop();
			aAssembler.StackContents.Push(new StackContent(4, aField.FieldType));
			if (aDerefExternalAddress && aField.IsExternalField) {
				new Pushd(CPUx86.Registers.AtEAX);
			} else {
				new Pushd(CPUx86.Registers.EAX);
			}
		}

		public static void Multiply(Assembler.Assembler aAssembler) {
			StackContent xStackContent = aAssembler.StackContents.Pop();
			new CPUx86.Xor("edx", "edx");
			if (xStackContent.IsFloat) {
				throw new Exception("Float support not yet supported!");
			} else {
				if (xStackContent.Size > 4) {
					new CPUx86.Pop("eax");
					new CPUx86.Add("esp", "4");
					new CPUx86.Multiply("dword [esp]");
					new CPUx86.Add("esp", "8");
					new Pushd("0");
					new Pushd("eax");
				} else {
					new CPUx86.Pop("eax");
					new CPUx86.Multiply("dword [esp]");
					new CPUx86.Add("esp", "4");
					new Pushd("eax");
				}
			}
		}

		public static void Ldfld(Assembler.Assembler aAssembler, TypeInformation aType, string aFieldName) {
			Ldfld(aAssembler, aType, aType.Fields[aFieldName]);
		}

		public static void Ldfld(Assembler.Assembler aAssembler, TypeInformation aType, TypeInformation.Field aField) {
			Ldfld(aAssembler, aType, aField, true);
		}

		public static void Ldfld(Assembler.Assembler aAssembler, TypeInformation aType, TypeInformation.Field aField, bool aAddGCCode) {
			Ldfld(aAssembler, aType, aField, aAddGCCode, true);
		}

		public static void Ldfld(Assembler.Assembler aAssembler, TypeInformation aType, TypeInformation.Field aField, bool aAddGCCode, bool aDerefExternalField) {
			aAssembler.StackContents.Pop();
			int aExtraOffset = 0;
			if (aType.NeedsGC) {
				aExtraOffset = 12;
			}
			new CPUx86.Pop("ecx");
			new CPUx86.Add("ecx", "0x" + (aField.Offset + aExtraOffset).ToString("X"));
			if (aField.IsExternalField && aDerefExternalField) {
				new CPUx86.Move("ecx", "[ecx]");
			}
			if (aField.Size >= 4) {
				for (int i = 1; i <= (aField.Size / 4); i++) {
					new CPUx86.Move("eax", "[ecx + 0x" + (aField.Size - (i * 4)).ToString("X") + "]");
					new CPUx86.Pushd("eax");
				}
				switch (aField.Size % 4) {
					case 1: {
							new CPUx86.Move("eax", "0");
							new CPUx86.Move("al", "[ecx]");
							new CPUx86.Push("eax");
							break;
						}
					case 2: {
							new CPUx86.Move("eax", "0");
							new CPUx86.Move("ax", "[ecx + 0x]");
							new CPUx86.Push("eax");
							break;
						}
					case 0: {
							break;
						}
					default:
						throw new Exception("Remainder size " + (aField.Size % 4) + " not supported!");
				}
			} else {
				switch (aField.Size) {
					case 1: {
							new CPUx86.Move("eax", "0");
							new CPUx86.Move("al", "[ecx]");
							new CPUx86.Push("eax");
							break;
						}
					case 2: {
							new CPUx86.Move("eax", "0");
							new CPUx86.Move("ax", "[ecx]");
							new CPUx86.Push("eax");
							break;
						}
					case 0: {
							break;
						}
					default:
						throw new Exception("Remainder size " + (aField.Size) + " not supported!");
				}
			}
			if (aAddGCCode && aField.NeedsGC) {
				new CPUx86.Pushd(Registers.AtESP);
				Engine.QueueMethod(GCImplementationRefs.IncRefCountRef);
				new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.IncRefCountRef));
			}
			aAssembler.StackContents.Push(new StackContent(aField.Size, aField.FieldType));
		}

		public static void Stfld(Assembler.Assembler aAssembler, TypeInformation aType, TypeInformation.Field aField) {
			aAssembler.StackContents.Pop();
			int xRoundedSize = aField.Size;
			if (xRoundedSize % 4 != 0) {
				xRoundedSize += 4 - (xRoundedSize % 4);
			}
			int aExtraOffset = 0;
			if (aType.NeedsGC) {
				aExtraOffset = 12;
				new CPUx86.Pushd("[esp + 4]");
				//Ldfld(aAssembler, aType, aField, false);
				new CPUx86.Pop("eax");
				new CPUx86.Pushd("[eax + " + (aField.Offset + aExtraOffset) + "]");
				Engine.QueueMethod(GCImplementationRefs.DecRefCountRef);
				new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.DecRefCountRef));
			}
			new CPUx86.Move("ecx", "[esp + 0x" + xRoundedSize.ToString("X") + "]");
			new CPUx86.Add("ecx", "0x" + (aField.Offset + aExtraOffset).ToString("X"));
			for (int i = 0; i < (aField.Size / 4); i++) {
				new CPUx86.Pop("eax");
				new Move("dword [ecx + 0x" + (i * 4).ToString("X") + "]", "eax");
			}
			switch (aField.Size % 4) {
				case 1: {
						new CPUx86.Pop("eax");
						new Move("byte [ecx + " + ((aField.Size / 4) * 4) + "]", "al");
						break;
					}
				case 2: {
						new CPUx86.Pop("eax");
						new Move("word [ecx + " + ((aField.Size / 4) * 4) + "]", "ax");
						break;
					}
				case 0: {
						break;
					}
				default:
					throw new Exception("Remainder size " + (aField.Size % 4) + " not supported!");

			}
			if (aField.NeedsGC) {
				new CPUx86.Pushd(Registers.ECX);
				new CPUx86.Pushd(Registers.EAX);
				new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.DecRefCountRef));
				new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.DecRefCountRef));
			}
			new CPUx86.Add("esp", "4");
			aAssembler.StackContents.Pop();
		}

		public static void AddWithOverflow(Assembler.Assembler aAssembler, bool signed) {
			throw new NotImplementedException();
			//Add(aAssembler);
			throw new NotImplementedException();
			//if (signed)
			//{
			//    new CPUx86.Interrupt(CPUx86.Interrupt.INTO);
			//} else
			//{
				
			//}
		}
		public static void Add(Assembler.Assembler aAssembler) {
			StackContent xSize = aAssembler.StackContents.Pop();
			if (xSize.IsFloat) {
				throw new Exception("Floats not yet supported!");
			}
			if (xSize.Size > 8) {
				throw new Exception("Size '" + xSize.Size + "' not supported");
			}
			if (xSize.Size > 4) {
				new CPUx86.Pop("eax");
				new CPUx86.Pop("edx");
				new CPUx86.Add("[esp]", "eax");
				new CPUx86.AddWithCarry("[esp + 4]", "edx");
			} else {
				new CPUx86.Pop("eax");
				new CPUx86.Add("[esp]", "eax");
			}
		}

		public static void Ldloc(Assembler.Assembler aAssembler, MethodInformation.Variable aLocal) {
			Ldloc(aAssembler, aLocal, true);
		}

		public static void Ldloc(Assembler.Assembler aAssembler, MethodInformation.Variable aLocal, bool aAddGCCode) {
            if (aLocal.VirtualAddresses.Length > 1)
            {
                foreach (string s in aLocal.VirtualAddresses)
                {
                    new CPUx86.Move("eax",
                                    "[" + s + "]");
                    new CPUx86.Push("eax");
                }
            }
            else {
                new CPUx86.Xor("eax",
                               "eax");

                switch (Engine.GetFieldStorageSize(aLocal.VariableType))
                {
                    case 1: {
                        new CPUx86.Move("al",
                                        "[" + aLocal.VirtualAddresses.First() + "]");
                        break;
                    }
                    case 2:
                        {
                            new CPUx86.Move("ax",
                                            "[" + aLocal.VirtualAddresses.First() + "]");
                            break;
                        }
                    case 4:
                        {
                            new CPUx86.Move("eax",
                                            "[" + aLocal.VirtualAddresses.First() + "]");
                            break;
                        }
                }
                new CPUx86.Push("eax");
                if (aAddGCCode && aLocal.IsReferenceType) {
                    new CPUx86.Push("eax");
                    Engine.QueueMethod(GCImplementationRefs.IncRefCountRef);
                    new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.IncRefCountRef));
                }
            }
            aAssembler.StackContents.Push(new StackContent(aLocal.Size,
                                               aLocal.VariableType));

		}

		protected override void AssembleHeader() {
			base.AssembleHeader();
			new Comment("Next Instruction = " + mNextInstructionLabel);
			string xCurExceptionFieldName = DataMember.GetStaticFieldName(IL2CPU.Assembler.Assembler.CurrentExceptionRef);
			if (mNeedsTypeCheck) {
				// call VTablesImpl.IsInstance to see the actual instance name..
				new CPUx86.Move("eax", "[" + xCurExceptionFieldName + "]");
				new CPUx86.Pushd(Registers.AtEAX);
				new CPUx86.Pushd(Engine.RegisterType(mCatchType).ToString());
				new CPUx86.Call(Label.GenerateLabelName(VTablesImplRefs.IsInstanceRef));
				new CPUx86.Compare(Registers.EAX, "0");
				new CPUx86.JumpIfEqual(mNextInstructionLabel);
			}
			if (mNeedsExceptionPush) {
				new CPUx86.Push("dword [" + xCurExceptionFieldName + "]");
				Assembler.StackContents.Push(new StackContent(4, typeof(Exception)));
			}
		}
	}
}