#region COPYRIGHT
//
//     THIS IS GENERATED BY TEMPLATE
//     
//     AUTHOR  :     ROYE
//     DATE       :     2010
//
//     COPYRIGHT (C) 2010, TIANXIAHOTEL TECHNOLOGIES CO., LTD. ALL RIGHTS RESERVED.
//
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using BEH = System.Text.Template.BinaryExpressionHelper;

namespace System.Text.Template
{
    public class BinaryArithmicExpression : BinaryExpression
    {
        private readonly string _operator;
        static readonly Dictionary<string, OperatorMethod[]> _operatorMethods = new Dictionary<string, OperatorMethod[]>();

        public BinaryArithmicExpression(string op, Expression left, Expression right)
            : base(left, right)
        {
            _operator = op;
        }

        static BinaryArithmicExpression()
        {
            #region Init

            _operatorMethods["+"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(decimal), BEH.CalcDecimal),
                    new OperatorMethod(typeof(string), BEH.CalcString),
                    new OperatorMethod(typeof(string), typeof(string), typeof(object), BEH.CalcStringObject),
                    new OperatorMethod(typeof(string), typeof(object), typeof(string), BEH.CalcObjectString)
                };

            _operatorMethods["-"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(decimal), BEH.CalcDecimal)
                };
            _operatorMethods["*"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(decimal), BEH.CalcDecimal)
                };
            _operatorMethods["/"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(decimal), BEH.CalcDecimal)
                };
            _operatorMethods["%"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(decimal), BEH.CalcDecimal)
                };
            _operatorMethods["<<"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), typeof(uint), typeof(int), BEH.CalcUInt32_Int32),
                    new OperatorMethod(typeof(long), typeof(long), typeof(int), BEH.CalcInt64_Int32),
                    new OperatorMethod(typeof(ulong), typeof(ulong), typeof(int), BEH.CalcUInt64_Int32)
                };
            _operatorMethods[">>"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), typeof(uint), typeof(int), BEH.CalcUInt32_Int32),
                    new OperatorMethod(typeof(long), typeof(long), typeof(int), BEH.CalcInt64_Int32),
                    new OperatorMethod(typeof(ulong), typeof(ulong), typeof(int), BEH.CalcUInt64_Int32)
                };
            _operatorMethods["=="] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(bool), typeof(int), typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(bool), typeof(uint), typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(bool), typeof(long), typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(bool), typeof(ulong), typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), typeof(float), typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(bool), typeof(double), typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(bool), typeof(decimal), typeof(decimal), BEH.CalcDecimal),
                    new OperatorMethod(typeof(bool), typeof(string), typeof(string), BEH.CalcString),
                    new OperatorMethod(typeof(bool), BEH.CalcBool)
                };
            _operatorMethods["!="] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(bool), typeof(int), typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(bool), typeof(uint), typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(bool), typeof(long), typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(bool), typeof(ulong), typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), typeof(float), typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(bool), typeof(double), typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(bool), typeof(decimal), typeof(decimal), BEH.CalcDecimal),
                    new OperatorMethod(typeof(bool), typeof(string), typeof(string), BEH.CalcString),
                    new OperatorMethod(typeof(bool), BEH.CalcBool)
                };
            _operatorMethods["<"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(bool), typeof(int), typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(bool), typeof(uint), typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(bool), typeof(long), typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(bool), typeof(ulong), typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), typeof(float), typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(bool), typeof(double), typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(bool), typeof(decimal), typeof(decimal), BEH.CalcDecimal),
                };
            _operatorMethods[">"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(bool), typeof(int), typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(bool), typeof(uint), typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(bool), typeof(long), typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(bool), typeof(ulong), typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), typeof(float), typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(bool), typeof(double), typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(bool), typeof(decimal), typeof(decimal), BEH.CalcDecimal),
                };
            _operatorMethods["<="] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(bool), typeof(int), typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(bool), typeof(uint), typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(bool), typeof(long), typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(bool), typeof(ulong), typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), typeof(float), typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(bool), typeof(double), typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(bool), typeof(decimal), typeof(decimal), BEH.CalcDecimal),
                };
            _operatorMethods[">="] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(bool), typeof(int), typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(bool), typeof(uint), typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(bool), typeof(long), typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(bool), typeof(ulong), typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), typeof(float), typeof(float), BEH.CalcFloat),
                    new OperatorMethod(typeof(bool), typeof(double), typeof(double), BEH.CalcDouble),
                    new OperatorMethod(typeof(bool), typeof(decimal), typeof(decimal), BEH.CalcDecimal),
                };
            _operatorMethods["&"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), BEH.CalcBool)
                };
            _operatorMethods["|"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), BEH.CalcBool)
                };
            _operatorMethods["^"] = new OperatorMethod[]
                {
                    new OperatorMethod(typeof(int), BEH.CalcInt32),
                    new OperatorMethod(typeof(uint), BEH.CalcUInt32),
                    new OperatorMethod(typeof(long), BEH.CalcInt64),
                    new OperatorMethod(typeof(ulong), BEH.CalcUInt64),
                    new OperatorMethod(typeof(bool), BEH.CalcBool)
                };

            #endregion
        }

        public override ValueExpression Evaluate(ITemplateContext context)
        {
            ValueExpression[] values = new ValueExpression[] { Left.Evaluate(context), Right.Evaluate(context) };
            Type type1 = values[0].Type;
            Type type2 = values[1].Type;

            bool nullable1 = type1.IsGenericType && type1.GetGenericTypeDefinition() == typeof(Nullable<>);
            bool nullable2 = type2.IsGenericType && type2.GetGenericTypeDefinition() == typeof(Nullable<>);

            type1 = Nullable.GetUnderlyingType(type1) ?? type1;
            type2 = Nullable.GetUnderlyingType(type2) ?? type2;

            bool isNullable = (nullable1 || nullable2);
            OperatorMethod operatorMethod = FindOperatorMethod(type1, type2);
            if (operatorMethod == null)
            {
                Type promotionType = null;

                if (type1 == typeof(decimal) || type2 == typeof(decimal))
                {
                    promotionType = typeof(decimal);
                }
                else if (type1 == typeof(double) || type2 == typeof(double))
                {
                    promotionType = typeof(double);
                }
                else if (type1 == typeof(float) || type2 == typeof(float))
                {
                    promotionType = typeof(float);
                }
                else if (type1 == typeof(ulong) || type2 == typeof(ulong))
                {
                    promotionType = typeof(ulong);
                }
                else if (type1 == typeof(long) || type2 == typeof(long))
                {
                    promotionType = typeof(long);
                }
                else if (type1 == typeof(uint) || type2 == typeof(uint) && (type1 == typeof(sbyte) || type2 == typeof(sbyte) || type1 == typeof(short) || type2 == typeof(short) || type1 == typeof(int) || type2 == typeof(int)))
                {
                    promotionType = typeof(long);
                }
                else if (type1 == typeof(uint) || type2 == typeof(uint))
                {
                    promotionType = typeof(uint);
                }
                else if (type1.IsPrimitive && type2.IsPrimitive && type1 != typeof(bool) && type2 != typeof(bool))
                {
                    promotionType = typeof(int);
                }

                if (promotionType != null)
                {
                    type1 = promotionType;
                    type2 = promotionType;
                }

                operatorMethod = FindOperatorMethod(type1, type2);
            }

            if (operatorMethod == null)
            {
                if (_operator == "==" || _operator == "!=")
                    return new ValueExpression(BEH.CalcObject(_operator, values[0].Value, values[1].Value), typeof(bool));

                throw new ArithmeticException();
            }

            Type returnType = operatorMethod.ReturnType;

            if (isNullable)
            {
                returnType = typeof(Nullable<>).MakeGenericType(returnType);

                //TODO: check specs for bool? values
                if (values[0].Value == null || values[1].Value == null)
                    return new ValueExpression(null, returnType);
            }

            object value1 = Convert.ChangeType(values[0].Value, operatorMethod.Type1);
            object value2 = Convert.ChangeType(values[1].Value, operatorMethod.Type2);

            return new ValueExpression(operatorMethod.Function(_operator, value1, value2), returnType);
        }

        private OperatorMethod FindOperatorMethod(Type type1, Type type2)
        {
            OperatorMethod[] operatorMethods = _operatorMethods[_operator];

            foreach (OperatorMethod operatorMethod in operatorMethods)
            {
                bool sameType1 = type1 == operatorMethod.Type1;
                bool sameType2 = type2 == operatorMethod.Type2;

                bool canConvert1 = operatorMethod.Type1.IsAssignableFrom(type1);
                bool canConvert2 = operatorMethod.Type2.IsAssignableFrom(type2);

                if ((sameType1 || canConvert1) && (sameType2 || canConvert2))
                {
                    return operatorMethod;
                }
            }

            return null;
        }

        public override string ToString()
        {
            return string.Format("({0} {1} {2})", Left, _operator, Right);
        }

        private class OperatorMethod
        {
            public delegate object Action(string op, object v1, object v2);

            public readonly Type Type1;
            public readonly Type Type2;
            public readonly Type ReturnType;
            public readonly Action Function;

            public OperatorMethod(Type type, Action function)
            {
                Type1 = type;
                Type2 = type;
                ReturnType = type;
                Function = function;
            }

            public OperatorMethod(Type returnType, Type type1, Type type2, Action function)
            {
                ReturnType = returnType;
                Type1 = type1;
                Type2 = type2;
                Function = function;
            }
        }
    }
}
