using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using VCommon.Collections;
using static System.Linq.Expressions.Expression;

namespace VCommon.Linq.Expressions.Predicate
{
    public sealed class PredicateBuilder<T>
    {
        public static readonly PredicateBuilder<T> Instance = new PredicateBuilder<T>();

        private PredicateBuilder()
        {
        }

        public PredicateWrap<T> In<TV>(Expression<Func<T, TV>> selector, IEnumerable<TV> checkIn)
        {
            var c = Constant(checkIn.WrapEnumerable());
            var p = selector.Parameters[0];
            var call = Call(typeof(Enumerable), "Contains", new[] { typeof(TV) }, c, selector.Body);
            var exp = Lambda<Func<T, bool>>(call, p);
            return exp;
        }

        public PredicateWrap<T> NotIn<TV>(Expression<Func<T, TV>> selector, IEnumerable<TV> checkIn)
        {
            var p = selector.Parameters[0];
            var values = Constant(checkIn.WrapEnumerable());
            var @in = Call(typeof(Enumerable), "Contains", new[] { typeof(TV) }, values, selector.Body);
            var not = Not(@in);
            var exp = Lambda<Func<T, bool>>(not, p);
            return exp;
        }

        public PredicateWrap<T> StringContains(Expression<Func<T, string>> selector, string contains)
        {
            if (string.IsNullOrWhiteSpace(contains)) return null;

            var stringContainsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) })
                                       ?? throw new SystemException("ILL SYSTEM LIB? string.Contains(string) NOT EXIST??");

            var parameterExp = selector.Parameters[0];
            var valExp = Constant(contains, typeof(string));

            var callExp = Call(selector.Body, stringContainsMethod, valExp);
            var lambda = Lambda<Func<T, bool>>(callExp, parameterExp);

            return lambda;
        }

        public PredicateWrap<T> Between<TV>(Expression<Func<T, TV>> selector, TV min, TV max, bool include = true)
        {
            if (null == min && null == max) return null;

            PredicateWrap<T> predicateWrap = null;

            if (null != min)
            {
                predicateWrap = include
                    ? GreaterThanOrEqual(selector, min)
                    : GreaterThan(selector, min);
            }

            if (null != max)
            {
                predicateWrap &= include
                    ? LessThanOrEqual(selector, max)
                    : LessThan(selector, max);
            }

            return predicateWrap;
        }

        public PredicateWrap<T> Equal<TV>(Expression<Func<T, TV>> selector, TV valueToCompare) => BinOp(Expression.Equal, selector, valueToCompare);

        public PredicateWrap<T> NotEqual<TV>(Expression<Func<T, TV>> selector, TV valueToCompare) => BinOp(Expression.NotEqual, selector, valueToCompare);

        public PredicateWrap<T> LessThan<TV>(Expression<Func<T, TV>> selector, TV valueToCompare) => BinOp(Expression.LessThan, selector, valueToCompare);

        public PredicateWrap<T> LessThanOrEqual<TV>(Expression<Func<T, TV>> selector, TV valueToCompare) => BinOp(Expression.LessThanOrEqual, selector, valueToCompare);

        public PredicateWrap<T> GreaterThan<TV>(Expression<Func<T, TV>> selector, TV valueToCompare) => BinOp(Expression.GreaterThan, selector, valueToCompare);

        public PredicateWrap<T> GreaterThanOrEqual<TV>(Expression<Func<T, TV>> selector, TV valueToCompare) => BinOp(Expression.GreaterThanOrEqual, selector, valueToCompare);

        private static PredicateWrap<T> BinOp<TV>(Func<Expression, Expression, Expression> op, Expression<Func<T, TV>> selector, TV valueToCompare)
        {
            var parameterExp = selector.Parameters[0];
            var valToCompareExp = Constant(valueToCompare, typeof(TV));

            var callExp = op(selector.Body, valToCompareExp);
            var lambda = Lambda<Func<T, bool>>(callExp, parameterExp);

            return lambda;
        }
    }
}
