//
// CilWorker.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

namespace Mono.Cecil.Cil {

	using System;
	using SR = System.Reflection;

	public sealed class CilWorker {

		MethodBody m_mbody;
		InstructionCollection m_instrs;

		internal CilWorker (MethodBody body)
		{
			m_mbody = body;
			m_instrs = m_mbody.Instructions;
		}

		public MethodBody GetBody ()
		{
			return m_mbody;
		}

		public Instruction Create (OpCode opcode)
		{
			if (opcode.OperandType != OperandType.InlineNone)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode);
		}

		public Instruction Create (OpCode opcode, TypeReference type)
		{
			if (type == null)
				throw new ArgumentNullException ("type");
			if (opcode.OperandType != OperandType.InlineType &&
				opcode.OperandType != OperandType.InlineTok)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, type);
		}

		public Instruction Create (OpCode opcode, CallSite site)
		{
			if (site == null)
				throw new ArgumentNullException ("site");
			if (opcode.Code != Code.Calli)
				throw new ArgumentException ("code");

			return FinalCreate (opcode, site);
		}

		public Instruction Create (OpCode opcode, MethodReference method)
		{
			if (method == null)
				throw new ArgumentNullException ("method");
			if (opcode.OperandType != OperandType.InlineMethod &&
				opcode.OperandType != OperandType.InlineTok)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, method);
		}

		public Instruction Create (OpCode opcode, FieldReference field)
		{
			if (field == null)
				throw new ArgumentNullException ("field");
			if (opcode.OperandType != OperandType.InlineField &&
				opcode.OperandType != OperandType.InlineTok)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, field);
		}

		public Instruction Create (OpCode opcode, string str)
		{
			if (str == null)
				throw new ArgumentNullException ("str");
			if (opcode.OperandType != OperandType.InlineString)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, str);
		}

		public Instruction Create (OpCode opcode, sbyte b)
		{
			if (opcode.OperandType != OperandType.ShortInlineI &&
				opcode != OpCodes.Ldc_I4_S)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, b);
		}

		public Instruction Create (OpCode opcode, byte b)
		{
			if (opcode.OperandType == OperandType.ShortInlineVar)
				return Create (opcode, m_mbody.Variables [b]);

			if (opcode.OperandType == OperandType.ShortInlineParam)
				return Create (opcode, CodeReader.GetParameter (m_mbody, b));

			if (opcode.OperandType != OperandType.ShortInlineI ||
				opcode == OpCodes.Ldc_I4_S)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, b);
		}

		public Instruction Create (OpCode opcode, int i)
		{
			if (opcode.OperandType == OperandType.InlineVar)
				return Create (opcode, m_mbody.Variables [i]);

			if (opcode.OperandType == OperandType.InlineParam)
				return Create (opcode, CodeReader.GetParameter (m_mbody, i));

			if (opcode.OperandType != OperandType.InlineI)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, i);
		}

		public Instruction Create (OpCode opcode, long l)
		{
			if (opcode.OperandType != OperandType.InlineI8)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, l);
		}

		public Instruction Create (OpCode opcode, float f)
		{
			if (opcode.OperandType != OperandType.ShortInlineR)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, f);
		}

		public Instruction Create (OpCode opcode, double d)
		{
			if (opcode.OperandType != OperandType.InlineR)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, d);
		}

		public Instruction Create (OpCode opcode, Instruction label)
		{
			if (label == null)
				throw new ArgumentNullException ("label");
			if (opcode.OperandType != OperandType.InlineBrTarget &&
				opcode.OperandType != OperandType.ShortInlineBrTarget)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, label);
		}

		public Instruction Create (OpCode opcode, Instruction [] labels)
		{
			if (labels == null)
				throw new ArgumentNullException ("labels");
			if (opcode.OperandType != OperandType.InlineSwitch)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, labels);
		}

		public Instruction Create (OpCode opcode, VariableDefinition var)
		{
			if (var == null)
				throw new ArgumentNullException ("var");
			if (opcode.OperandType != OperandType.ShortInlineVar &&
				opcode.OperandType != OperandType.InlineVar)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, var);
		}

		public Instruction Create (OpCode opcode, ParameterDefinition param)
		{
			if (param == null)
				throw new ArgumentNullException ("param");
			if (opcode.OperandType != OperandType.ShortInlineParam &&
				opcode.OperandType != OperandType.InlineParam)
				throw new ArgumentException ("opcode");

			return FinalCreate (opcode, param);
		}

		static Instruction FinalCreate (OpCode opcode)
		{
			return FinalCreate (opcode, null);
		}

		static Instruction FinalCreate (OpCode opcode, object operand)
		{
			return new Instruction (opcode, operand);
		}

		public Instruction Emit (OpCode opcode)
		{
			Instruction instr = Create (opcode);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, TypeReference type)
		{
			Instruction instr = Create (opcode, type);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, MethodReference meth)
		{
			Instruction instr = Create (opcode, meth);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, CallSite site)
		{
			Instruction instr = Create (opcode, site);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, FieldReference field)
		{
			Instruction instr = Create (opcode, field);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, string str)
		{
			Instruction instr = Create (opcode, str);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, byte b)
		{
			Instruction instr = Create (opcode, b);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, sbyte b)
		{
			Instruction instr = Create (opcode, b);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, int i)
		{
			Instruction instr = Create (opcode, i);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, long l)
		{
			Instruction instr = Create (opcode, l);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, float f)
		{
			Instruction instr = Create (opcode, f);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, double d)
		{
			Instruction instr = Create (opcode, d);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, Instruction target)
		{
			Instruction instr = Create (opcode, target);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, Instruction [] targets)
		{
			Instruction instr = Create (opcode, targets);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, VariableDefinition var)
		{
			Instruction instr = Create (opcode, var);
			Append (instr);
			return instr;
		}

		public Instruction Emit (OpCode opcode, ParameterDefinition param)
		{
			Instruction instr = Create (opcode, param);
			Append (instr);
			return instr;
		}

		public void InsertBefore (Instruction target, Instruction instr)
		{
			int index = m_instrs.IndexOf (target);
			if (index == -1)
				throw new ArgumentOutOfRangeException ("Target instruction not in method body");

			m_instrs.Insert (index, instr);
			instr.Previous = target.Previous;
			if (target.Previous != null)
				target.Previous.Next = instr;
			target.Previous = instr;
			instr.Next = target;
		}

		public void InsertAfter (Instruction target, Instruction instr)
		{
			int index = m_instrs.IndexOf (target);
			if (index == -1)
				throw new ArgumentOutOfRangeException ("Target instruction not in method body");

			m_instrs.Insert (index + 1, instr);
			instr.Next = target.Next;
			if (target.Next != null)
				target.Next.Previous = instr;
			target.Next = instr;
			instr.Previous = target;
		}

		public void Append (Instruction instr)
		{
			Instruction last = null, current = instr;
			if (m_instrs.Count > 0)
				last = m_instrs [m_instrs.Count - 1];

			if (last != null) {
				last.Next = instr;
				current.Previous = last;
			}

			m_instrs.Add (current);
		}

		public void Replace (Instruction old, Instruction instr)
		{
			int index = m_instrs.IndexOf (old);
			if (index == -1)
				throw new ArgumentOutOfRangeException ("Target instruction not in method body");

			InsertAfter (old, instr);
			Remove (old);
		}

		public void Remove (Instruction instr)
		{
			if (!m_instrs.Contains (instr))
				throw new ArgumentException ("Instruction not in method body");

			if (instr.Previous != null)
				instr.Previous.Next = instr.Next;
			if (instr.Next != null)
				instr.Next.Previous = instr.Previous;
			m_instrs.Remove (instr);
		}
	}
}
