using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;

namespace Indy.IL2CPU.IL {
	public partial class ILReader {
		private readonly MethodBody mBody;
		private MemoryStream mStream;
		private MethodBase mMethod;
		private Module mModule;
		public ILReader(MethodBase aMethod) {
			mBody = aMethod.GetMethodBody();
			mMethod = aMethod;
			mModule = mMethod.Module;
			mStream = new MemoryStream(mBody.GetILAsByteArray());
		}

		private OpCodeEnum mOpCode;
		private byte[] mOperand;
		private bool mHasOperand;

		public int Position {
			get;
			private set;
		}

		public int NextPosition {
			get {
				return (int)mStream.Position;
			}
		}

		public bool EndOfStream {
			get {
				return mStream.Position == mStream.Length;
			}
		}

		public bool HasOperand {
			get {
				return mHasOperand;
			}
		}

		public OpCodeEnum OpCode {
			get {
				return mOpCode;
			}
		}

		public byte[] Operand {
			get {
				return mOperand;
			}
		}

		private string mOperandValueStr;
		public string OperandValueStr {
			get {
				if (mOperandValueStr == null) {
					mOperandValueStr = mModule.ResolveString(OperandValueInt32);
				}
				return mOperandValueStr;
			}
		}

		private MethodBase mOperandValueMethod;
		public MethodBase OperandValueMethod {
			get {
				if (mOperandValueMethod == null) {
					Type[] xTypeGenArgs = null;
					Type[] xMethodGenArgs = null;
					if (mMethod.DeclaringType.IsGenericType) {
						xTypeGenArgs = mMethod.DeclaringType.GetGenericArguments();
					}
					if (mMethod.IsGenericMethod) {
						xMethodGenArgs = mMethod.GetGenericArguments();
					}
					mOperandValueMethod = mModule.ResolveMethod(OperandValueInt32, xTypeGenArgs, xMethodGenArgs);
				}
				return mOperandValueMethod;
			}
		}

		private uint? mOperandValueBranchPosition;
		private bool mIsShortcut;
		public uint OperandValueBranchPosition {
			get {
				if (mOperandValueBranchPosition == null) {
					//sbyte xTemp = (sbyte)mOperand;
					//if (xTemp == mOperand) {
					//    mOperandValueBranchPosition = NextPosition + xTemp;
					//} else {
					//						if (mStream.Length < (NextPosition + mOperand + 1)) {
					//							mOperandValueBranchPosition = (uint)mOperand;
					//						} else {
					//							mOperandValueBranchPosition = (uint)(NextPosition + mOperand);
					//						}
					if (!mIsShortcut) {
						mOperandValueBranchPosition = (uint?)(NextPosition + OperandValueInt32);
					} else {
						mOperandValueBranchPosition = (uint?)(NextPosition + (sbyte)OperandValueInt32);
					}
					//}
				}
				return mOperandValueBranchPosition.Value;
			}
		}

		private FieldInfo mOperandValueField;
		public FieldInfo OperandValueField {
			get {
				if (mOperandValueField == null) {
					Type[] xTypeGenArgs = null;
					Type[] xMethodGenArgs = null;
					if (mMethod.DeclaringType.IsGenericType) {
						xTypeGenArgs = mMethod.DeclaringType.GetGenericArguments();
					}
					if (mMethod.IsGenericMethod) {
						xMethodGenArgs = mMethod.GetGenericArguments();
					}
					mOperandValueField = mModule.ResolveField(OperandValueInt32, xTypeGenArgs, xMethodGenArgs);
				}
				return mOperandValueField;
			}
		}

		private Type mOperandValueType;
		public Type OperandValueType {
			get {
				if (mOperandValueType == null) {
					Type[] xTypeGenArgs = null;
					Type[] xMethodGenArgs = null;
					if (mMethod.DeclaringType.IsGenericType) {
						xTypeGenArgs = mMethod.DeclaringType.GetGenericArguments();
					}
					if (mMethod.IsGenericMethod) {
						xMethodGenArgs = mMethod.GetGenericArguments();
					}
					mOperandValueType = mModule.ResolveType(OperandValueInt32, xTypeGenArgs, xMethodGenArgs);
				}
				return mOperandValueType;
			}
		}

		public uint[] OperandValueBranchLocations {
			get;
			private set;
		}

		private int? mOperandValueInt32;
		public int OperandValueInt32 {
            get
            {
                if (!mIsShortcut)
                {
                    if (mOperandValueInt32 == null)
                    {
                        byte[] xData = new byte[4];
                        Array.Copy(Operand, xData, Math.Min(4, Operand.Length));
                        mOperandValueInt32 = BitConverter.ToInt32(xData, 0);
                    }
                }
                else 
                { 
                    sbyte xShortValue = (sbyte)Operand[0]; 
                    mOperandValueInt32 = xShortValue; 
                }
                return mOperandValueInt32.Value;
            }
		}

		private Single? mOperandValueSingle;
		public Single OperandValueSingle {
			get {
				if (mOperandValueSingle == null) {
					mOperandValueSingle = BitConverter.ToSingle(Operand, 0);
				}
				return mOperandValueSingle.Value;
			}
		}

		private Double? mOperandValueDouble;
		public Double OperandValueDouble {
			get {
				if (mOperandValueDouble == null) {
					mOperandValueDouble = BitConverter.ToDouble(Operand, 0);
				}
				return mOperandValueDouble.Value;
			}
		}

		public bool Read() {
			Position = NextPosition;
			int xByteValueInt = mStream.ReadByte();
			OpCodeEnum xOpCode;
			if (xByteValueInt == -1) {
				return false;
			}
			byte xByteValue = (byte)xByteValueInt;
			if (xByteValue == 0xFE) {
				xByteValueInt = mStream.ReadByte();
				if (xByteValueInt == -1) {
					return false;
				}
				xOpCode = (OpCodeEnum)(xByteValue << 8 | xByteValueInt);
			} else {
				xOpCode = (OpCodeEnum)xByteValue;
			}
			byte xOperandSize = GetOperandSize(xOpCode);
			mOperand = null;
			mOperandValueStr = null;
			mOperandValueMethod = null;
			mOperandValueField = null;
			mOperandValueSingle = null;
			mOperandValueType = null;
			mOperandValueInt32 = null;
			mOperandValueBranchPosition = null;
			OperandValueBranchLocations = null;
			mOperandValueDouble = null;
			mOpCode = GetNonShortcutOpCode(xOpCode);
			mIsShortcut = mOpCode != xOpCode;
			mHasOperand = xOperandSize > 0;
			if (mHasOperand) {
				mOperand = ReadOperand(xOperandSize);
				mOperandValueInt32 = GetInt32FromOperandByteArray(mOperand);
			} else {
				if (mOpCode != xOpCode) {
					long? xTempOperand = GetShortcutOperand(xOpCode);
					if (xTempOperand != null) {
						mHasOperand = true;
						mOperand = BitConverter.GetBytes(xTempOperand.Value);
					}
				}
				if (mOpCode == OpCodeEnum.Switch) {
					int[] xBranchLocations1 = new int[ReadInt32()];
					for (int i = 0; i < xBranchLocations1.Length; i++) {
						xBranchLocations1[i] = ReadInt32();
					}
					uint[] xResult = new uint[xBranchLocations1.Length];
					for (int i = 0; i < xBranchLocations1.Length; i++) {
						if ((NextPosition + xBranchLocations1[i]) < 0) {
							xResult[i] = (uint)xBranchLocations1[i];
						} else {
							xResult[i] = (uint)(NextPosition + xBranchLocations1[i]);
						}
					}
					OperandValueBranchLocations = xResult;
				}
			}
			return true;
		}


		private Int64 ReadInt64() {
			long xResult = 0;
			byte xOperandSize = 64;
			byte[] xBytes = new byte[xOperandSize / 8];
			while (xOperandSize > 0) {
				int xByteValueInt = mStream.ReadByte();
				if (xByteValueInt == -1) {
					break;
				}
				xBytes[(xOperandSize / 8) - 1] = (byte)xByteValueInt;
				xOperandSize -= 8;
			}
			for (int i = 0; i < xBytes.Length; i++) {
				xResult = xResult << 8 | xBytes[i];
			}
			return xResult;
		}

		private byte[] ReadOperand(byte aOperandSize) {
			byte[] xBytes = new byte[aOperandSize / 8];
			int index = 0;
			while (aOperandSize > 0) {
				int xByteValueInt = mStream.ReadByte();
				if (xByteValueInt == -1) {
					break;
				}
				xBytes[index] = (byte)xByteValueInt;
				index += 1;
				aOperandSize -= 8;
			}
			return xBytes;
		}

		private static Int32 GetInt32FromOperandByteArray(byte[] aData) {
			Int32 xResult = 0;
			for (int i = aData.Length - 1; i >= 0; i--) {
				xResult = xResult << 8 | aData[i];
			}
			return xResult;
		}

		private Int32 ReadInt32() {
			return GetInt32FromOperandByteArray(ReadOperand(32));
		}
	}
}