using System;
using Mono.Cecil.Cil;
using Mono.Cecil.Metadata;

namespace Mono.Debugger.Soft
{
	internal class ILInterpreter
	{
		MethodMirror method;

		public ILInterpreter (MethodMirror method) {
			this.method = method;
		}

		public Value Evaluate (Value this_val, Value[] args) {
			var body = method.GetMethodBody ();

			// Implement only the IL opcodes required to evaluate mcs compiled property accessors:
			// IL_0000:  nop
			// IL_0001:  ldarg.0
			// IL_0002:  ldfld      int32 Tests::field_i
			// IL_0007:  stloc.0
			// IL_0008:  br         IL_000d
			// IL_000d:  ldloc.0
			// IL_000e:  ret
			// ... or returns a simple constant:
			// IL_0000:  ldc.i4 1024
			// IL_0005:  conv.i8
			// IL_0006:  ret
			if (args != null && args.Length != 0)
				throw new NotSupportedException ();

			if (method.IsStatic || method.DeclaringType.IsValueType || this_val == null || !(this_val is ObjectMirror))
				throw new NotSupportedException ();

			var instructions = body.Instructions;
			if (instructions.Count < 1 || instructions.Count > 16)
				throw new NotSupportedException ();

			var stack = new Value [16];
			var ins = instructions [0];
			Value locals_0 = null;
			int ins_count = 0;
			int sp = 0;

			while (ins != null) {
				if (ins_count > 16)
					throw new NotImplementedException ();

				var next = ins.Next;
				ins_count++;

				var op = ins.OpCode;
				if (op == OpCodes.Nop) {
				} else if (op == OpCodes.Ldarg_0) {
					if (sp != 0)
						throw new NotSupportedException ();

					stack [sp++] = this_val;
				} else if (op == OpCodes.Ldfld) {
					if (sp != 1)
						throw new NotSupportedException ();

					var obj = (ObjectMirror) stack [--sp];
					var field = (FieldInfoMirror) ins.Operand;
					try {
						stack [sp++] = obj.GetValue (field);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_0) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 0);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_1) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 1);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_2) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 2);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_3) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 3);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_4) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 4);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_5) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 5);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_6) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 6);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_7) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 7);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_8) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, 8);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_M1) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, -1);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, ins.Operand);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I4_S) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, ins.Operand);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_I8) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, ins.Operand);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_R4) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, ins.Operand);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Ldc_R8) {
					if (sp != 0)
						throw new NotSupportedException ();

					try {
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, ins.Operand);
					} catch (ArgumentException) {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_I) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToInt32 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_I1) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToSByte (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_U1) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToByte (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_I2) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToInt16 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_U2) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToUInt16 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_I4) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToInt32 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_U4) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToUInt32 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_I8) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToInt64 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_U8) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToUInt64 (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_R4) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToSingle (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Conv_R8) {
					if (sp != 1)
						throw new NotSupportedException ();

					try {
						var primitive = (PrimitiveValue) stack [--sp];
						stack [sp++] = new PrimitiveValue (method.VirtualMachine, Convert.ToDouble (primitive.Value));
					} catch {
						throw new NotSupportedException ();
					}
				} else if (op == OpCodes.Stloc_0) {
					if (sp != 1)
						throw new NotSupportedException ();

					locals_0 = stack [--sp];
				} else if (op == OpCodes.Br || op == OpCodes.Br_S) {
					next = (ILInstruction) ins.Operand;
				} else if (op == OpCodes.Ldloc_0) {
					if (sp != 0)
						throw new NotSupportedException ();

					stack [sp++] = locals_0;
				} else if (op == OpCodes.Ret) {
					if (sp > 0) {
						var res = stack [--sp];

						var primitive = res as PrimitiveValue;
						if (method.ReturnType.IsPrimitive && primitive != null) {
							// cast the primitive value to the return type
							try {
								switch (method.ReturnType.CSharpName) {
								case "double": res = new PrimitiveValue (method.VirtualMachine, Convert.ToDouble (primitive.Value)); break;
								case "float": res = new PrimitiveValue (method.VirtualMachine, Convert.ToSingle (primitive.Value)); break;
								case "ulong": res = new PrimitiveValue (method.VirtualMachine, Convert.ToUInt64 (primitive.Value)); break;
								case "long": res = new PrimitiveValue (method.VirtualMachine, Convert.ToInt64 (primitive.Value)); break;
								case "uint": res = new PrimitiveValue (method.VirtualMachine, Convert.ToUInt32 (primitive.Value)); break;
								case "int": res = new PrimitiveValue (method.VirtualMachine, Convert.ToInt32 (primitive.Value)); break;
								case "ushort": res = new PrimitiveValue (method.VirtualMachine, Convert.ToUInt16 (primitive.Value)); break;
								case "short": res = new PrimitiveValue (method.VirtualMachine, Convert.ToInt16 (primitive.Value)); break;
								case "sbyte": res = new PrimitiveValue (method.VirtualMachine, Convert.ToSByte (primitive.Value)); break;
								case "byte": res = new PrimitiveValue (method.VirtualMachine, Convert.ToByte (primitive.Value)); break;
								case "char": res = new PrimitiveValue (method.VirtualMachine, Convert.ToChar (primitive.Value)); break;
								case "bool": res = new PrimitiveValue (method.VirtualMachine, Convert.ToBoolean (primitive.Value)); break;
								}
							} catch {
								throw new NotSupportedException ();
							}
						}

						return res;
					}

					return null;
				} else {
					throw new NotSupportedException ();
				}

				ins = next;
			}

			return null;
		}
	}
}
