using System;
using System.Linq.Expressions;
using VCommon.Linq.Expressions.Predicate.Internals;

namespace VCommon.Linq.Expressions.Predicate
{
    public static class PredicateWrap
    {
        public static PredicateWrap<T> Op<T>(this Expression<Func<T, bool>> exp) => new PredicateWrap<T>(exp);
    }

    public class PredicateWrap<T>
    {
        private readonly Expression<Func<T, bool>> _expression;

        internal PredicateWrap(Expression<Func<T, bool>> expression) => _expression = expression;

        public PredicateWrap<T> When(bool condition) => condition ? this : null;

        public static implicit operator Expression<Func<T, bool>>(PredicateWrap<T> me)
        {
            var expression = me?._expression;
            if (true == expression?.CanReduce) return (Expression<Func<T, bool>>)expression.Reduce();
            return expression;
        }

        public static implicit operator PredicateWrap<T>(Expression<Func<T, bool>> me) => me == null ? null : new PredicateWrap<T>(me);

        public static PredicateWrap<T> operator &(PredicateWrap<T> a, PredicateWrap<T> b)
        {
            var aIsNull = null == a?._expression;
            var bIsNull = null == b?._expression;

            if (aIsNull && bIsNull) return null;

            if (aIsNull) return b;
            if (bIsNull) return a;

            return new PredicateWrap<T>(a._expression.And(b._expression));
        }

        public static PredicateWrap<T> operator &(PredicateWrap<T> a, Expression<Func<T, bool>> b)
        {
            var aIsNull = null == a?._expression;
            var bIsNull = null == b;

            if (aIsNull && bIsNull) return null;

            if (aIsNull) return b;
            if (bIsNull) return a;

            return new PredicateWrap<T>(a._expression.And(b));
        }

        public static PredicateWrap<T> operator &(Expression<Func<T, bool>> a, PredicateWrap<T> b)
        {
            var aIsNull = null == a;
            var bIsNull = null == b?._expression;

            if (aIsNull && bIsNull) return null;

            if (aIsNull) return b;
            if (bIsNull) return a;

            return new PredicateWrap<T>(a.And(b._expression));
        }

        public static PredicateWrap<T> operator |(PredicateWrap<T> a, PredicateWrap<T> b)
        {
            var aIsNull = null == a?._expression;
            var bIsNull = null == b?._expression;

            if (aIsNull && bIsNull) return null;

            if (aIsNull) return b;
            if (bIsNull) return a;

            return new PredicateWrap<T>(a._expression.Or(b._expression));
        }

        public static PredicateWrap<T> operator |(PredicateWrap<T> a, Expression<Func<T, bool>> b)
        {
            var aIsNull = null == a?._expression;
            var bIsNull = null == b;

            if (aIsNull && bIsNull) return null;

            if (aIsNull) return b;
            if (bIsNull) return a;

            return new PredicateWrap<T>(a._expression.Or(b));
        }

        public static PredicateWrap<T> operator |(Expression<Func<T, bool>> a, PredicateWrap<T> b)
        {
            var aIsNull = null == a;
            var bIsNull = null == b?._expression;

            if (aIsNull && bIsNull) return null;

            if (aIsNull) return b;
            if (bIsNull) return a;

            return new PredicateWrap<T>(a.Or(b._expression));
        }
    }
}
