using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DapperExtensions.Mapper;
using DapperExtensions.Sql;

namespace DapperExtensions
{
    /// <summary>
    /// 谓词类
    /// </summary>
    public static class Predicates
    {
        /// <summary>
        /// Factory method that creates a new IFieldPredicate predicate: [FieldName] [Operator] [Value]. 
        /// Example: WHERE FirstName = 'Foo'
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="expression">An expression that returns the left operand [FieldName].</param>
        /// <param name="op">The comparison operator.</param>
        /// <param name="value">The value for the predicate.</param>
        /// <param name="not">Effectively inverts the comparison operator. Example: WHERE FirstName &lt;&gt; 'Foo'.</param>
        /// <returns>An instance of IFieldPredicate.</returns>
        public static IFieldPredicate Field<T>(Expression<Func<T, object>> expression, Operator op, object value, bool not = false) where T : class
        {
            PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            return new FieldPredicate<T>
            {
                PropertyName = propertyInfo.Name,
                Operator = op,
                Value = value,
                Not = not
            };
        }

        /// <summary>
        /// Factory method that creates a new IPropertyPredicate predicate: [FieldName1] [Operator] [FieldName2]
        /// Example: WHERE FirstName = LastName
        /// </summary>
        /// <typeparam name="T">The type of the entity for the left operand.</typeparam>
        /// <typeparam name="T2">The type of the entity for the right operand.</typeparam>
        /// <param name="expression">An expression that returns the left operand [FieldName1].</param>
        /// <param name="op">The comparison operator.</param>
        /// <param name="expression2">An expression that returns the right operand [FieldName2].</param>
        /// <param name="not">Effectively inverts the comparison operator. Example: WHERE FirstName &lt;&gt; LastName </param>
        /// <returns>An instance of IPropertyPredicate.</returns>
        public static IPropertyPredicate Property<T, T2>(Expression<Func<T, object>> expression, Operator op, Expression<Func<T2, object>> expression2, bool not = false)
            where T : class
            where T2 : class
        {
            PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            PropertyInfo propertyInfo2 = ReflectionHelper.GetProperty(expression2) as PropertyInfo;
            return new PropertyPredicate<T, T2>
            {
                PropertyName = propertyInfo.Name,
                PropertyName2 = propertyInfo2.Name,
                Operator = op,
                Not = not
            };
        }

        /// <summary>
        /// Factory method that creates a new IPredicateGroup predicate.
        /// Predicate groups can be joined together with other predicate groups.
        /// </summary>
        /// <param name="op">The grouping operator to use when joining the predicates (AND / OR).</param>
        /// <param name="predicate">A list of predicates to group.</param>
        /// <returns>An instance of IPredicateGroup.</returns>
        public static IPredicateGroup Group(GroupOperator op, params IPredicate[] predicate)
        {
            if (predicate == null || predicate.Length == 0)
            {
                return null;
            }
            return new PredicateGroup
            {
                Operator = op,
                Predicates = predicate
            };
        }

        /// <summary>
        /// Factory method that creates a new IExistsPredicate predicate.
        /// </summary>
        public static IExistsPredicate Exists<TSub>(IPredicate predicate, bool not = false)
            where TSub : class
        {
            return new ExistsPredicate<TSub>
            {
                Not = not,
                Predicate = predicate
            };
        }

        /// <summary>
        /// Factory method that creates a new IBetweenPredicate predicate. 
        /// </summary>
        public static IBetweenPredicate Between<T>(Expression<Func<T, object>> expression, BetweenValues values, bool not = false)
            where T : class
        {
            PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            return new BetweenPredicate<T>
            {
                Not = not,
                PropertyName = propertyInfo.Name,
                Value = values
            };
        }

        /// <summary>
        /// Factory method that creates a new Sort which controls how the results will be sorted.
        /// </summary>
        /// <typeparam name="T">约束</typeparam>
        /// <param name="expression">表达式树</param>
        /// <param name="ascending">排序</param>
        /// <returns></returns>
        public static ISort Sort<T>(Expression<Func<T, object>> expression, bool ascending = true)
        {
            PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            return new Sort
            {
                PropertyName = propertyInfo.Name,
                Ascending = ascending
            };
        }
    }

    /// <summary>
    /// 谓词接口
    /// </summary>
    public interface IPredicate
    {
        /// <summary>
        /// 获取Sql语句
        /// </summary>
        /// <param name="sqlGenerator">sql生产接口对象</param>
        /// <param name="parameters">参数字典</param>
        /// <param name="sqlDialect">sql语句配置接口</param>
        /// <returns></returns>
        string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect);
    }

    /// <summary>
    /// 基础谓词接口
    /// </summary>
    public interface IBasePredicate : IPredicate
    {
        /// <summary>
        /// 属性名称
        /// </summary>
        string PropertyName { get; set; }
    }

    /// <summary>
    /// 基础谓词
    /// </summary>
    public abstract class BasePredicate : IBasePredicate
    {
        /// <summary>
        /// 获取Sql字符串
        /// </summary>
        /// <param name="sqlGenerator">Sql生产接口对象</param>
        /// <param name="parameters">参数字典</param>
        /// <param name="sqlDialect">Sql语句配置接口对象</param>
        /// <returns></returns>
        public abstract string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect);

        /// <summary>
        /// 属性名称
        /// </summary>
        public string PropertyName { get; set; }

        /// <summary>
        /// 获取字段名称
        /// </summary>
        /// <param name="entityType">实体类型</param>
        /// <param name="sqlGenerator">Sql生产接口对象</param>
        /// <param name="propertyName">属性名称</param>
        /// <returns></returns>
        protected virtual string GetColumnName(Type entityType, ISqlGenerator sqlGenerator, string propertyName)
        {
            IClassMapper map = sqlGenerator.Configuration.GetMap(entityType);
            if (map == null)
            {
                throw new NullReferenceException(string.Format("Map was not found for {0}", entityType));
            }

            IPropertyMap propertyMap = map.Properties.SingleOrDefault(p => p.Value.Name == propertyName).Value;
            if (propertyMap == null)
            {
                throw new NullReferenceException(string.Format("{0} was not found for {1}", propertyName, entityType));
            }

            return sqlGenerator.GetColumnName(map, propertyMap, false);
        }
    }

    /// <summary>
    /// 比较谓词接口
    /// </summary>
    public interface IComparePredicate : IBasePredicate
    {
        /// <summary>
        /// 操作符枚举
        /// </summary>
        Operator Operator { get; set; }

        /// <summary>
        /// Not
        /// </summary>
        bool Not { get; set; }
    }

    /// <summary>
    /// 比较谓词类继承基类谓词类
    /// </summary>
    public abstract class ComparePredicate : BasePredicate
    {
        /// <summary>
        /// 比较符枚举
        /// </summary>
        public Operator Operator { get; set; }

        /// <summary>
        /// Not
        /// </summary>
        public bool Not { get; set; }

        /// <summary>
        /// 获取操作符字符串
        /// </summary>
        /// <returns></returns>
        public virtual string GetOperatorString()
        {
            switch (Operator)
            {
                case Operator.Gt:
                    return Not ? "<=" : ">";
                case Operator.Ge:
                    return Not ? "<" : ">=";
                case Operator.Lt:
                    return Not ? ">=" : "<";
                case Operator.Le:
                    return Not ? ">" : "<=";
                case Operator.Like:
                    return Not ? "NOT LIKE" : "LIKE";
                default:
                    return Not ? "<>" : "=";
            }
        }
    }

    /// <summary>
    /// 字段谓词几块
    /// </summary>
    public interface IFieldPredicate : IComparePredicate
    {
        /// <summary>
        /// 值
        /// </summary>
        object Value { get; set; }
    }

    /// <summary>
    /// 字段谓词泛型类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class FieldPredicate<T> : ComparePredicate, IFieldPredicate
        where T : class
    {
        /// <summary>
        /// 值
        /// </summary>
        public object Value { get; set; }

        /// <summary>
        /// 获取Sql命令
        /// </summary>
        /// <param name="sqlGenerator">Sql生产接口对象</param>
        /// <param name="parameters">参数字典</param>
        /// <param name="sqlDialect">Sql语句配置接口</param>
        /// <returns></returns>
        public override string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect)
        {
            string columnName = GetColumnName(typeof(T), sqlGenerator, PropertyName);
            if (Value == null)
            {
                return string.Format("({0} IS {1}NULL)", columnName, Not ? "NOT " : string.Empty);
            }

            if (Value is IEnumerable && !(Value is string))
            {
                if (Operator != Operator.Eq)
                {
                    throw new ArgumentException("Operator must be set to Eq for Enumerable types");
                }

                List<string> @params = new List<string>();
                foreach (var value in (IEnumerable)Value)
                {
                    string valueParameterName = parameters.SetParameterName(PropertyName, value, sqlDialect);
                    @params.Add(valueParameterName);
                }

                string paramStrings = @params.Aggregate(new StringBuilder(), (sb, s) => sb.Append((sb.Length != 0 ? ", " : string.Empty) + s), sb => sb.ToString());
                return string.Format("({0} {1}IN ({2}))", columnName, Not ? "NOT " : string.Empty, paramStrings);
            }
            string parameterName = parameters.SetParameterName(PropertyName, Value, sqlDialect);
            return string.Format("({0} {1} {2})", columnName, GetOperatorString(), parameterName);
        }
    }

    /// <summary>
    /// 属性谓词接口
    /// </summary>
    public interface IPropertyPredicate : IComparePredicate
    {
        /// <summary>
        /// 属性名称
        /// </summary>
        string PropertyName2 { get; set; }
    }

    /// <summary>
    /// 属性谓词泛型类
    /// </summary>
    /// <typeparam name="T">约束</typeparam>
    /// <typeparam name="T2">约束</typeparam>
    public class PropertyPredicate<T, T2> : ComparePredicate, IPropertyPredicate
        where T : class
        where T2 : class
    {
        /// <summary>
        /// 属性名称
        /// </summary>
        public string PropertyName2 { get; set; }

        /// <summary>
        /// 获取Sql命令
        /// </summary>
        /// <param name="sqlGenerator">Sql生产接口对象</param>
        /// <param name="parameters">参数字典</param>
        /// <param name="sqlDialect">Sql语句配置接口</param>
        /// <returns></returns>
        public override string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect)
        {
            string columnName = GetColumnName(typeof(T), sqlGenerator, PropertyName);
            string columnName2 = GetColumnName(typeof(T2), sqlGenerator, PropertyName2);
            return string.Format("({0} {1} {2})", columnName, GetOperatorString(), columnName2);
        }
    }

    /// <summary>
    /// BetweenAnd结构体
    /// </summary>
    public struct BetweenValues
    {
        /// <summary>
        /// 开始值
        /// </summary>
        public object Value1 { get; set; }

        /// <summary>
        /// 结束值
        /// </summary>
        public object Value2 { get; set; }
    }

    /// <summary>
    /// Between谓词接口
    /// </summary>
    public interface IBetweenPredicate : IPredicate
    {
        /// <summary>
        /// 属性名称
        /// </summary>
        string PropertyName { get; set; }

        /// <summary>
        /// BetweenAnd结构体
        /// </summary>
        BetweenValues Value { get; set; }

        /// <summary>
        /// Not
        /// </summary>
        bool Not { get; set; }
    }

    /// <summary>
    /// Between谓词泛型类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BetweenPredicate<T> : BasePredicate, IBetweenPredicate
        where T : class
    {
        /// <summary>
        /// 获取Sql
        /// </summary>
        /// <param name="sqlGenerator">Sql生产接口</param>
        /// <param name="parameters">参数列表</param>
        /// <param name="sqlDialect">Sql语句配置接口对象</param>
        /// <returns></returns>
        public override string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect)
        {
            string columnName = GetColumnName(typeof(T), sqlGenerator, PropertyName);
            string propertyName1 = parameters.SetParameterName(PropertyName, Value.Value1, sqlDialect);
            string propertyName2 = parameters.SetParameterName(PropertyName, Value.Value2, sqlDialect);
            return string.Format("({0} {1}BETWEEN {2} AND {3})", columnName, Not ? "NOT " : string.Empty, propertyName1, propertyName2);
        }

        /// <summary>
        /// BetweenAnd结构体
        /// </summary>
        public BetweenValues Value { get; set; }

        /// <summary>
        /// Not
        /// </summary>
        public bool Not { get; set; }
    }

    /// <summary>
    /// Comparison operator for predicates.
    /// </summary>
    public enum Operator
    {
        /// <summary>
        /// 等于
        /// </summary>
        Eq,

        /// <summary>
        /// 大于
        /// </summary>
        Gt,

        /// <summary>
        /// 大于或等于
        /// </summary>
        Ge,

        /// <summary>
        /// 小于
        /// </summary>
        Lt,

        /// <summary>
        /// 小于或等于
        /// </summary>
        Le,

        /// <summary>
        /// Like (You can use % in the value to do wilcard searching)
        /// </summary>
        Like
    }

    /// <summary>
    /// 分组谓词接口
    /// </summary>
    public interface IPredicateGroup : IPredicate
    {
        /// <summary>
        /// 分组操作符枚举
        /// </summary>
        GroupOperator Operator { get; set; }

        /// <summary>
        /// 谓词接口List
        /// </summary>
        IList<IPredicate> Predicates { get; set; }
    }

    /// <summary>
    /// Groups IPredicates together using the specified group operator.
    /// </summary>
    public class PredicateGroup : IPredicateGroup
    {
        /// <summary>
        /// 分组操作符枚举
        /// </summary>
        public GroupOperator Operator { get; set; }

        /// <summary>
        /// 谓词接口List
        /// </summary>
        public IList<IPredicate> Predicates { get; set; }

        /// <summary>
        /// 获取Sql
        /// </summary>
        /// <param name="sqlGenerator">Sql生产接口</param>
        /// <param name="parameters">参数列表</param>
        /// <param name="sqlDialect">Sql语句配置接口对象</param>
        /// <returns></returns>
        public string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect)
        {
            string seperator = Operator == GroupOperator.And ? " AND " : " OR ";
            return "(" + Predicates.Aggregate(new StringBuilder(),
                                        (sb, p) => (sb.Length == 0 ? sb : sb.Append(seperator)).Append(p.GetSql(sqlGenerator, parameters, sqlDialect)),
                                        sb => sb.ToString()) + ")";
        }
    }

    /// <summary>
    /// Exists谓词接口
    /// </summary>
    public interface IExistsPredicate : IPredicate
    {
        /// <summary>
        /// 谓词接口对象
        /// </summary>
        IPredicate Predicate { get; set; }

        /// <summary>
        /// Not
        /// </summary>
        bool Not { get; set; }
    }

    /// <summary>
    /// Exists谓词泛型类
    /// </summary>
    /// <typeparam name="TSub"></typeparam>
    public class ExistsPredicate<TSub> : IExistsPredicate where TSub : class
    {
        /// <summary>
        /// 谓词接口对象
        /// </summary>
        public IPredicate Predicate { get; set; }

        /// <summary>
        /// Not
        /// </summary>
        public bool Not { get; set; }

        /// <summary>
        /// EXISTS-Sql
        /// </summary>
        /// <param name="sqlGenerator">Sql生产接口</param>
        /// <param name="parameters">参数字典</param>
        /// <param name="sqlDialect">Sql语句配置接口</param>
        /// <returns></returns>
        public string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters, ISqlDialect sqlDialect)
        {
            IClassMapper mapSub = GetClassMapper(typeof(TSub), sqlGenerator.Configuration);
            string sql = string.Format("({0}EXISTS (SELECT 1 FROM {1} WHERE {2}))",
                Not ? "NOT " : string.Empty,
                sqlGenerator.GetTableName(mapSub),
                Predicate.GetSql(sqlGenerator, parameters, sqlDialect));
            return sql;
        }

        /// <summary>
        /// 获取数据库配置接口
        /// </summary>
        /// <param name="type">类型</param>
        /// <param name="configuration">Dapper扩展配置接口</param>
        /// <returns></returns>
        protected virtual IClassMapper GetClassMapper(Type type, IDapperExtensionConfiguration configuration)
        {
            IClassMapper map = configuration.GetMap(type);
            if (map == null)
            {
                throw new NullReferenceException(string.Format("Map was not found for {0}", type));
            }
            return map;
        }
    }

    /// <summary>
    /// 排序接口
    /// </summary>
    public interface ISort
    {
        /// <summary>
        /// 属性名称
        /// </summary>
        string PropertyName { get; set; }

        /// <summary>
        /// 升序
        /// </summary>
        bool Ascending { get; set; }
    }

    /// <summary>
    /// 排序实现
    /// </summary>
    public class Sort : ISort
    {
        /// <summary>
        /// 属性名称
        /// </summary>
        public string PropertyName { get; set; }

        /// <summary>
        /// 升序
        /// </summary>
        public bool Ascending { get; set; }
    }

    /// <summary>
    /// Operator to use when joining predicates in a PredicateGroup.
    /// </summary>
    public enum GroupOperator
    {
        /// <summary>
        /// And
        /// </summary>
        And,

        /// <summary>
        /// Or
        /// </summary>
        Or
    }
}