#region License
// Copyright (c) 2004 Griffin Caprio & Choy Rim. All rights reserved.
// until we figure out what to put here
#endregion
#region Imports
using System;
using System.Collections;
using System.Reflection.Emit;
#endregion

namespace DotNetMock.Dynamic.Generate
{
	/// <summary>
	/// Utility functions for emitting IL.
	/// </summary>
	public class ILUtils
	{
		/// <summary>
		/// Return appropriate ldind <see cref="OpCode"/> for type
		/// referenced by address on top of stack (of emitted code).
		/// </summary>
		/// <param name="referencedType">type referenced by address
		/// on the top of the stack (of emitted code)</param>
		/// <returns>appropriate ldind <see cref="OpCode"/> that will
		/// load the stack with value in the address that is expected
		/// to be on the stack</returns>
		/// <remarks>
		/// If <see cref="OpCodes.Ldobj"/> is returned, then you must
		/// emit the <see cref="ValueType"/> with it.
		/// </remarks>
		public static OpCode GetLdindOpCodeForType(Type referencedType) 
		{
			// short-circuit reference types
			if ( ! referencedType.IsValueType ) 
			{
				return OpCodes.Ldind_Ref;
			}
			// must be a value type
			// check among primitive types
			// ensure table is initialized
			if (ldindOpCodes == null) 
			{
				ldindOpCodes = new Hashtable();
				ldindOpCodes[typeof(sbyte)]  = OpCodes.Ldind_I1;
				ldindOpCodes[typeof(short)]  = OpCodes.Ldind_I2;
				ldindOpCodes[typeof(int)]    = OpCodes.Ldind_I4;
				ldindOpCodes[typeof(long)]   = OpCodes.Ldind_I8;
				ldindOpCodes[typeof(byte)]   = OpCodes.Ldind_U1;
				ldindOpCodes[typeof(ushort)] = OpCodes.Ldind_U2;
				ldindOpCodes[typeof(uint)]   = OpCodes.Ldind_U4;
				ldindOpCodes[typeof(ulong)]  = OpCodes.Ldind_I8;
				ldindOpCodes[typeof(float)]  = OpCodes.Ldind_R4;
				ldindOpCodes[typeof(double)] = OpCodes.Ldind_R8;
				ldindOpCodes[typeof(char)]   = OpCodes.Ldind_U2;
				ldindOpCodes[typeof(bool)]   = OpCodes.Ldind_I1;
			}
			object opCodeObject = ldindOpCodes[referencedType];
			if (opCodeObject != null)
			{
				return (OpCode) opCodeObject;
			}
			// must be non-primitive ValueType
			return OpCodes.Ldobj;
		}

		/// <summary>
		/// Return appropriate stind <see cref="OpCode"/> for type
		/// on top of stack (of emitted code).
		/// </summary>
		/// <param name="referencedType">type 
		/// on the top of the stack (of emitted code)</param>
		/// <returns>appropriate stind <see cref="OpCode"/> that will
		/// store the value on the stack in the address below it</returns>
		/// <remarks>
		/// If <see cref="OpCodes.Stobj"/> is returned, then you must
		/// emit the <see cref="ValueType"/> with it.
		/// </remarks>
		public static OpCode GetStindOpCodeForType(Type referencedType) 
		{
			// short-circuit reference types
			if ( ! referencedType.IsValueType ) 
			{
				return OpCodes.Stind_Ref;
			}
			// must be a value type, first check among primitives
			// ensure primitive opcodes table is initialized
			if (stindOpCodes == null) 
			{
				stindOpCodes = new Hashtable();
				stindOpCodes[typeof(sbyte)]  = OpCodes.Stind_I1;
				stindOpCodes[typeof(short)]  = OpCodes.Stind_I2;
				stindOpCodes[typeof(int)]    = OpCodes.Stind_I4;
				stindOpCodes[typeof(long)]   = OpCodes.Stind_I8;
				stindOpCodes[typeof(byte)]   = OpCodes.Stind_I1;
				stindOpCodes[typeof(ushort)] = OpCodes.Stind_I2;
				stindOpCodes[typeof(uint)]   = OpCodes.Stind_I4;
				stindOpCodes[typeof(ulong)]  = OpCodes.Stind_I8;
				stindOpCodes[typeof(float)]  = OpCodes.Stind_R4;
				stindOpCodes[typeof(double)] = OpCodes.Stind_R8;
				stindOpCodes[typeof(char)]   = OpCodes.Stind_I2;
				stindOpCodes[typeof(bool)]   = OpCodes.Stind_I1;
			}
			object opCodeObject = stindOpCodes[referencedType];
			if (opCodeObject != null)
			{
				return (OpCode) opCodeObject;
			}
			// must be non-primitive ValueType
			return OpCodes.Stobj;
		}

		/// <summary>
		/// Emit the appropriate ldind instruction(s) to load the
		/// specified type from the address on the stack.
		/// </summary>
		/// <param name="il">generator to emit code to</param>
		/// <param name="referencedType">the type in the address on
		/// the top of the stack</param>
		public static void EmitTypedLdind(ILGenerator il, Type referencedType) 
		{
			OpCode opCode = GetLdindOpCodeForType(referencedType);
			if ( opCode.Equals(OpCodes.Ldobj) ) 
			{
				il.Emit(opCode, referencedType);
			}
			else 
			{
				il.Emit(opCode);
			}
		}

		/// <summary>
		/// Emit the appropriate stind instruction(s) to store the
		/// specified type from the top of the stack to the address
		/// bwlow it.
		/// </summary>
		/// <param name="il">generator to emit code to</param>
		/// <param name="referencedType">type of the value on the top of
		/// the stack</param>
		public static void EmitTypedStind(ILGenerator il, Type referencedType) 
		{
			OpCode opCode = GetStindOpCodeForType(referencedType);
			if ( opCode.Equals(OpCodes.Stobj) ) 
			{
				il.Emit(opCode, referencedType);
			}
			else 
			{
				il.Emit(opCode);
			}
		}

		private static IDictionary ldindOpCodes = null;
		private static IDictionary stindOpCodes = null;
	}
}
