using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DapperExtensions.Lambda
{
    /// <summary>
    /// ExpressionHasher
    /// </summary>
    public class ExpressionHasher : ExpressionVisitor
    {
        /// <summary>
        /// Hash
        /// </summary>
        /// <param name="exp">表达式</param>
        /// <returns></returns>
        public int Hash(System.Linq.Expressions.Expression exp)
        {
            this.HashCode = 0;
            this.Visit(exp);
            return this.HashCode;
        }


        /// <summary>
        /// HashCode
        /// </summary>
        public int HashCode { get; protected set; }

        /// <summary>
        /// Hash
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual ExpressionHasher Hash(int value)
        {
            unchecked { this.HashCode += value; }
            return this;
        }

        /// <summary>
        /// Hash
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual ExpressionHasher Hash(bool value)
        {
            unchecked { this.HashCode += value ? 1 : 0; }
            return this;
        }

        private static readonly object s_nullValue = new object();

        /// <summary>
        /// Hash
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected virtual ExpressionHasher Hash(object value)
        {
            value = value ?? s_nullValue;
            unchecked { this.HashCode += value.GetHashCode(); }
            return this;
        }

        /// <summary>
        /// 重写Visit
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression Visit(System.Linq.Expressions.Expression exp)
        {
            if (exp == null) return exp;

            this.Hash((int)exp.NodeType).Hash(exp.Type);
            return base.Visit(exp);
        }

        /// <summary>
        /// 重写VisitBinary
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitBinary(BinaryExpression b)
        {
            this.Hash(b.IsLifted).Hash(b.IsLiftedToNull).Hash(b.Method);
            return base.VisitBinary(b);
        }

        /// <summary>
        /// 重写VisitBinding
        /// </summary>
        /// <param name="binding"></param>
        /// <returns></returns>
        protected override MemberBinding VisitBinding(MemberBinding binding)
        {
            this.Hash(binding.BindingType).Hash(binding.Member);
            return base.VisitBinding(binding);
        }

        /// <summary>
        /// 重写VisitConstant
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitConstant(ConstantExpression c)
        {
            this.Hash(c.Value);
            return base.VisitConstant(c);
        }

        /// <summary>
        /// 重写VisitElementInitializer
        /// </summary>
        /// <param name="initializer"></param>
        /// <returns></returns>
        protected override ElementInit VisitElementInitializer(ElementInit initializer)
        {
            this.Hash(initializer.AddMethod);
            return base.VisitElementInitializer(initializer);
        }

        /// <summary>
        /// 重写VisitLambda
        /// </summary>
        /// <param name="lambda"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitLambda(LambdaExpression lambda)
        {
            foreach (var p in lambda.Parameters)
            {
                this.VisitParameter(p);
            }

            return base.VisitLambda(lambda);
        }

        /// <summary>
        /// 重写VisitMemberAccess
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitMemberAccess(MemberExpression m)
        {
            this.Hash(m.Member);
            return base.VisitMemberAccess(m);
        }

        /// <summary>
        /// 重写VisitMethodCall
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitMethodCall(MethodCallExpression m)
        {
            this.Hash(m.Method);
            return base.VisitMethodCall(m);
        }

        /// <summary>
        /// 重写VisitNew
        /// </summary>
        /// <param name="nex"></param>
        /// <returns></returns>
        protected override NewExpression VisitNew(NewExpression nex)
        {
            this.Hash(nex.Constructor);
            if (nex.Members != null)
            {
                foreach (var m in nex.Members) this.Hash(m);
            }

            return base.VisitNew(nex);
        }

        /// <summary>
        /// 重写VisitParameter
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitParameter(ParameterExpression p)
        {
            this.Hash(p.Name);
            return base.VisitParameter(p);
        }

        /// <summary>
        /// 重写VisitTypeIs
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitTypeIs(TypeBinaryExpression b)
        {
            this.Hash(b.TypeOperand);
            return base.VisitTypeIs(b);
        }

        /// <summary>
        /// 重写VisitUnary
        /// </summary>
        /// <param name="u"></param>
        /// <returns></returns>
        protected override System.Linq.Expressions.Expression VisitUnary(UnaryExpression u)
        {
            this.Hash(u.IsLifted).Hash(u.IsLiftedToNull).Hash(u.Method);
            return base.VisitUnary(u);
        }
    }
}