//
// CompileVisitor.cs
//
// Authors:
//	Chris Bacon (chrisbacon76@gmail.com)
//
// Copyright (C) 2010 Chris Bacon
//
// 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.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil.Cil;
using Mono.CodeContracts.Rewrite.Ast;

namespace Mono.CodeContracts.Rewrite.AstVisitors {
	class CompileVisitor : ExprVisitor {

		public CompileVisitor (ILProcessor il, Dictionary<Expr, Instruction> instructionLookup)
			: this (il, instructionLookup, il.Append)
		{
		}

		public CompileVisitor (ILProcessor il, Dictionary<Expr, Instruction> instructionLookup, Action<Instruction> fnEmit)
		{
			this.il = il;
			this.instructionLookup = instructionLookup;
			this.fnEmit = fnEmit;
		}

		private ILProcessor il;
		private Dictionary<Expr, Instruction> instructionLookup;
		private Action<Instruction> fnEmit;

		private void Emit (Expr originalExpr, Instruction inst)
		{
			Instruction originalInst;
			if (this.instructionLookup != null) {
				// TODO: Doesn't handle inherited contracts - need to check what to do in this case.
				//if (this.instructionLookup.TryGetValue (originalExpr, out originalInst)) {
				//	inst.SequencePoint = originalInst.SequencePoint;
				//}
			}
			this.fnEmit (inst);
		}

		private void Emit (Expr originalExpr, Func<Instruction> fnCreateInstruction)
		{
			Instruction inst = fnCreateInstruction();
			this.Emit (originalExpr, inst);
		}

		private void Emit (Expr originalExpr, Func<IEnumerable<Instruction>> fnCreateInstruction)
		{
			throw new NotImplementedException ();
		}

		protected override Expr VisitNop (ExprNop e)
		{
			var instNop = this.il.Create (OpCodes.Nop);
			this.Emit (e, instNop);
			return e;
		}

		protected override Expr VisitLoadArg (ExprLoadArg e)
		{
			this.Emit (e, () => {
				int index = e.Index;
				switch (index) {
				case 0:
					return this.il.Create (OpCodes.Ldarg_0);
				case 1:
					return this.il.Create (OpCodes.Ldarg_1);
				case 2:
					return this.il.Create (OpCodes.Ldarg_2);
				case 3:
					return this.il.Create (OpCodes.Ldarg_3);
				default:
					if (e.Index <= 255) {
						return this.il.Create (OpCodes.Ldarg_S, (byte) index);
					} else {
						return this.il.Create (OpCodes.Ldarg, index);
					}
				}
			});
			
			return e;
		}

		protected override Expr VisitLoadConstant (ExprLoadConstant e)
		{
			this.Emit (e, () => {
				object v = e.Value;
				if (v == null) {
					return this.il.Create (OpCodes.Ldnull);
				}
				Type vType = v.GetType ();
				TypeCode vTypeCode = Type.GetTypeCode (vType);
				switch (vTypeCode) {
				case TypeCode.Int32:
					int value = (int) v;
					switch (value) {
					case -1:
						return this.il.Create (OpCodes.Ldc_I4_M1);
					case 0:
						return this.il.Create (OpCodes.Ldc_I4_0);
					case 1:
						return this.il.Create (OpCodes.Ldc_I4_1);
					case 2:
						return this.il.Create (OpCodes.Ldc_I4_2);
					case 3:
						return this.il.Create (OpCodes.Ldc_I4_3);
					case 4:
						return this.il.Create (OpCodes.Ldc_I4_4);
					case 5:
						return this.il.Create (OpCodes.Ldc_I4_5);
					case 6:
						return this.il.Create (OpCodes.Ldc_I4_6);
					case 7:
						return this.il.Create (OpCodes.Ldc_I4_7);
					case 8:
						return this.il.Create (OpCodes.Ldc_I4_8);
					default:
						if (value >= -128 && value <= 127) {
							return this.il.Create (OpCodes.Ldc_I4_S, (sbyte) value);
						} else {
							return this.il.Create (OpCodes.Ldc_I4, value);
						}
					}
				case TypeCode.Single:
					return this.il.Create (OpCodes.Ldc_R4, (float) v);
				case TypeCode.Double:
					return this.il.Create (OpCodes.Ldc_R8, (double) v);
				case TypeCode.String:
					return this.il.Create (OpCodes.Ldstr, (string) v);
				default:
					throw new NotSupportedException ("Cannot handle constant: " + vTypeCode);
				}
			});

			return e;
		}

		private Expr VisitBinary (ExprBinaryOp e, Func<Instruction> fnCreateIl)
		{
			this.Visit (e.Left);
			this.Visit (e.Right);
			var inst = fnCreateIl ();
			this.Emit (e, inst);
			return e;
		}

		protected override Expr VisitCompareLessThan (ExprCompareLessThan e)
		{
			return this.VisitBinary (e, () => this.il.Create (e.IsSigned ? OpCodes.Clt : OpCodes.Clt_Un));
		}

		protected override Expr VisitCompareGreaterThan (ExprCompareGreaterThan e)
		{
			return this.VisitBinary (e, () => this.il.Create (e.IsSigned ? OpCodes.Cgt : OpCodes.Cgt_Un));
		}

		protected override Expr VisitCompareEqual (ExprCompareEqual e)
		{
			return this.VisitBinary (e, () => this.il.Create (OpCodes.Ceq));
		}

		protected override Expr VisitAdd (ExprAdd e)
		{
			return this.VisitBinary (e, () => {
				if (!e.Overflow) {
					return this.il.Create (OpCodes.Add);
				} else {
					return this.il.Create (e.IsSigned ? OpCodes.Add_Ovf : OpCodes.Add_Ovf_Un);
				}
			});
		}

		protected override Expr VisitSub (ExprSub e)
		{
			return this.VisitBinary (e, () => {
				if (!e.Overflow) {
					return this.il.Create (OpCodes.Sub);
				} else {
					return this.il.Create (e.IsSigned ? OpCodes.Sub_Ovf : OpCodes.Sub_Ovf_Un);
				}
			});
		}

		protected override Expr VisitCall (ExprCall e)
		{
			foreach (var param in e.Parameters) {
				this.Visit (param);
			}
			var instCall = this.il.Create (OpCodes.Call, e.Method);
			this.Emit (e, instCall);
			return e;
		}

		protected override Expr VisitReturn (ExprReturn e)
		{
			var instReturn = this.il.Create (OpCodes.Ret);
			this.Emit (e, instReturn);
			return e;
		}

		protected override Expr VisitBox (ExprBox e)
		{
			this.Visit (e.ExprToBox);
			var instBox = this.il.Create (OpCodes.Box, e.ReturnType);
			this.Emit (e, instBox);
			return e;
		}

		protected override Expr VisitConv (ExprConv e)
		{
			this.Visit (e.ExprToConvert);
			Instruction instConv;
			switch (e.ConvToType) {
			case TypeCode.Int32:
				instConv = this.il.Create (OpCodes.Conv_I4);
				break;
			case TypeCode.Int64:
				instConv = this.il.Create (OpCodes.Conv_I8);
				break;
			default:
				throw new NotSupportedException ("Cannot conv to: " + e.ConvToType);
			}
			this.Emit (e, instConv);
			return e;
		}

	}
}
