using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace InABox.Core
{
    public interface ISubQuery
    {
        Type GetQueryType();

        IFilter? GetFilter();

        IColumn GetColumn();
    }

    public class SubQuery<T> : ISubQuery
    {
        public SubQuery()
        {
            Filter = null;
            Column = null;
        }

        public SubQuery(Filter<T>? filter, Column<T> column)
        {
            Filter = filter;
            Column = column;
        }

        public Filter<T>? Filter { get; set; }
        public Column<T> Column { get; set; }

        public Type GetQueryType() => typeof(T);

        public IFilter? GetFilter() => Filter;

        public IColumn GetColumn() => Column;

        public override string ToString()
        {
            return $"{typeof(T).Name}.{Column} WHERE {Filter?.AsOData()}";
        }
    }

    public class SubQuerySerializationWrapper
    {
        public SubQuerySerializationWrapper(string type, string subquery)
        {
            Type = type;
            SerializedSubquery = subquery;
        }

        public string Type { get; set; }
        public string SerializedSubquery { get; set; }
    }


    public enum Operator
    {
        IsEqualTo,
        IsNotEqualTo,
        IsGreaterThan,
        IsGreaterThanOrEqualTo,
        IsLessThan,
        IsLessThanOrEqualTo,
        BeginsWith,
        Contains,
        EndsWith,
        InList,
        NotInList,
        InQuery,
        All,
        None
    }


    public enum ListOperator
    {
        Includes,
        Excludes
    }

    public enum FilterConstant
    {
        Today,
        Now,
    }

    public interface IFilter
    {
        Expression Expression { get; set; }
        Operator Operator { get; set; }
        object? Value { get; set; }

        IEnumerable<IFilter> Ands { get; }
        IEnumerable<IFilter> Ors { get; }

        public IFilter And(IFilter filter);
        public IFilter And<T>(Expression<Func<T,object>> expression);
        public IFilter And(string property);

        public IFilter Or(IFilter filter);
        public IFilter Or<T>(Expression<Func<T,object>> expression);
        
        IFilter IsEqualTo(bool value);
        IFilter IsEqualTo(string value);
        IFilter IsEqualTo(Guid value);
        IFilter IsEqualTo(int value);
        IFilter IsEqualTo(DateTime value);
        IFilter IsEqualTo(TimeSpan value);
        IFilter IsEqualTo<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter IsEqualTo(object value);
        
        IFilter IsNotEqualTo(bool value);
        IFilter IsNotEqualTo(string value);
        IFilter IsNotEqualTo(Guid value);
        IFilter IsNotEqualTo(int value);
        IFilter IsNotEqualTo(DateTime value);
        IFilter IsNotEqualTo(TimeSpan value);
        IFilter IsNotEqualTo<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter IsNotEqualTo(object value);
        
        IFilter IsGreaterThan(bool value);
        IFilter IsGreaterThan(string value);
        IFilter IsGreaterThan(Guid value);
        IFilter IsGreaterThan(int value);
        IFilter IsGreaterThan(DateTime value);
        IFilter IsGreaterThan(TimeSpan value);
        IFilter IsGreaterThan<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter IsGreaterThan(object value);
        
        IFilter IsGreaterThanOrEqualTo(bool value);
        IFilter IsGreaterThanOrEqualTo(string value);
        IFilter IsGreaterThanOrEqualTo(Guid value);
        IFilter IsGreaterThanOrEqualTo(int value);
        IFilter IsGreaterThanOrEqualTo(DateTime value);
        IFilter IsGreaterThanOrEqualTo(TimeSpan value);
        IFilter IsGreaterThanOrEqualTo<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter IsGreaterThanOrEqualTo(object value);
        
        IFilter IsLessThan(bool value);
        IFilter IsLessThan(string value);
        IFilter IsLessThan(Guid value);
        IFilter IsLessThan(int value);
        IFilter IsLessThan(DateTime value);
        IFilter IsLessThan(TimeSpan value);
        IFilter IsLessThan<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter IsLessThan(object value);
         
        IFilter IsLessThanOrEqualTo(bool value);
        IFilter IsLessThanOrEqualTo(string value);
        IFilter IsLessThanOrEqualTo(Guid value);
        IFilter IsLessThanOrEqualTo(int value);
        IFilter IsLessThanOrEqualTo(DateTime value);
        IFilter IsLessThanOrEqualTo(TimeSpan value);
        IFilter IsLessThanOrEqualTo<TEnum>(TEnum value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter IsLessThanOrEqualTo(object value);    
        
        IFilter InList(params bool[] value);
        IFilter InList(params string[] value);
        IFilter InList(params Guid[] value);
        IFilter InList(params int[] value);
        IFilter InList(params DateTime[] value);
        IFilter InList(params TimeSpan[] value);
        IFilter InList<TEnum>(params TEnum[] value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter InList(params object[] value);    
        
        IFilter NotInList(params bool[] value);
        IFilter NotInList(params string[] value);
        IFilter NotInList(params Guid[] value);
        IFilter NotInList(params int[] value);
        IFilter NotInList(params DateTime[] value);
        IFilter NotInList(params TimeSpan[] value);
        IFilter NotInList<TEnum>(params TEnum[] value) where TEnum : struct, IConvertible, IComparable, IFormattable;
        IFilter NotInList(params object[] value);   
        
        IFilter BeginsWith(object value);   
        IFilter BeginsWith(string value); 
        
        IFilter Contains(object value);   
        IFilter Contains(string value);
        
        IFilter EndsWith(object value);   
        IFilter EndsWith(string value);

        IFilter InQuery<U>(SubQuery<U> value);
        IFilter InQuery<U>(Filter<U> filter, Expression<Func<U, object?>> column);

        IFilter All();
        IFilter None();

        string AsOData();
        
        IEnumerable<string> ColumnNames();
    }

    public static class Filter
    {
        public static IFilter Create<T>(Type concrete, Expression<Func<T,object>> expression)
        {
            if (!typeof(T).IsAssignableFrom(concrete))
                throw new Exception($"Filter: {concrete.EntityName()} does not implement {typeof(T).EntityName()}");
            var type = typeof(Filter<>).MakeGenericType(concrete);
            var property = CoreUtils.GetFullPropertyName(expression,".");
            return (Activator.CreateInstance(type, property) as IFilter)!;            
        }
        public static IFilter Create(Type concrete)
        {
            var type = typeof(Filter<>).MakeGenericType(concrete);
            return (Activator.CreateInstance(type) as IFilter)!;
        }
    }

    public interface IFilter2<T>
    {

    }

    public class Filter<T> : SerializableExpression<T>, IFilter
    {
        
        public Filter<T>? Parent { get; set; }

        public List<Filter<T>> Ands { get; private set; }

        public List<Filter<T>> Ors { get; private set; }

        public Operator Operator { get; set; }
        
        public object? Value { get; set; }

        IEnumerable<IFilter> IFilter.Ands => Ands;

        IEnumerable<IFilter> IFilter.Ors => Ors;

        public IFilter And<T1>(Expression<Func<T1, object>> expression)
        {
            return And(CoreUtils.GetFullPropertyName(expression, "."));
        }

        public IFilter And(IFilter filter)
        {
            if (filter is Filter<T> filterT)
                return And(filterT);
            throw new Exception($"Cannot add filter of type {filter.GetType()} to Filter<{typeof(T)}>");
        }

        IFilter IFilter.And(string property) => And(property);

        public Filter<T> TextSearch(string terms, params Expression<Func<T, string>>[] expressions)
        {
            //String[] comps = terms.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            //foreach (var comp in comps)
            //{
            //    Filter<T> search = null;
            //    foreach (var expression in expressions)
            //        search = search == null ? new Filter<T>(expression).Contains(comp.Trim()) : search.Or(expression).Contains(comp.Trim());
            //    Ands.Add(search);
            //}            
            Filter<T>? search = null;
            foreach (var expression in expressions)
                search = search == null
                    ? new Filter<T>(CoreUtils.GetFullPropertyName(expression, ".")).Contains(terms.Trim())
                    : search.Or(CoreUtils.GetFullPropertyName(expression, ".")).Contains(terms.Trim());
            if(search != null)
                Ands.Add(search);
            return this;
        }

        public Filter<T> And(Filter<T> filter)
        {
            filter.Parent = this;
            Ands.Add(filter);
            return this;
        }
        

        public Filter<T> And(Expression<Func<T, object?>> expression)
        {
            var and = new Filter<T>(expression);
            and.Parent = this;
            Ands.Add(and);
            return and;
        }

        public Filter<T> And(string property)
        {
            var and = new Filter<T>(property);
            and.Parent = this;
            Ands.Add(and);
            return and;
        }

        public static Filter<T>? And(params Filter<T>?[] filters)
        {
            Filter<T>? result = null;
            foreach (var filter in filters)
            {
                if(filter != null)
                {
                    if (result is null)
                        result = filter;
                    else
                        result.Ands.Add(filter);
                }
            }

            return result;
        }

        public IFilter Or<T1>(Expression<Func<T1, object>> expression)
        {
            return Or(CoreUtils.GetFullPropertyName(expression, "."));
        }

        public IFilter Or(IFilter filter)
        {
            if (filter is Filter<T> filterT)
                return Or(filterT);
            throw new Exception($"Cannot add filter of type {filter.GetType()} to Filter<{typeof(T)}>");
        }

        public Filter<T> Or(Filter<T> filter)
        {
            filter.Parent = this;
            Ors.Add(filter);
            return this;
        }

        public Filter<T> Or(Expression<Func<T, object?>> expression)
        {
            var or = new Filter<T>(expression);
            or.Parent = this;
            Ors.Add(or);
            return or;
        }

        public Filter<T> Or(string property)
        {
            var or = new Filter<T>(property);
            or.Parent = this;
            Ors.Add(or);
            return or;
        }

        private Filter<T> ApplyOperator<TValue>(Operator action, TValue value) where TValue : struct
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyOperator<TValue>(Operator action, TValue[] value) where TValue : struct
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyEnumOperator<TValue>(Operator action, TValue value) where TValue : struct, IConvertible, IComparable, IFormattable
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyEnumOperator<TValue>(Operator action, TValue[] value) where TValue : struct, IConvertible, IComparable, IFormattable
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyStringOperator(Operator action, string value)
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyStringOperator(Operator action, string[] value)
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyObjectOperator(Operator action, object value)
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyObjectOperator(Operator action, object[] value)
        {
            Operator = action;
            Value = value;
            return Parent ?? this;
        }

        private Filter<T> ApplyNullOperator(Operator action)
        {
            Operator = action;
            Value = null;
            return Parent ?? this;
        }

        //public override void GetObjectData(SerializationInfo info, StreamingContext context)
        //{
        //	base.GetObjectData(info, context);

        //	if (info != null)
        //		info.AddValue("ErrorMessage", this.ErrorMessage);
        //}


        public Filter<T1> Attach<T1>(Expression<Func<T1, object>> anchor)
        {
            var result = new Filter<T1>();
            result.Expression = CoreUtils.ConvertExpression(typeof(T), Expression, typeof(T1), anchor);
            result.Operator = Operator;
            result.Value = Value;
            foreach (var and in Ands)
                and.Attach(anchor);
            foreach (var or in Ors)
                or.Attach(anchor);
            return result;
        }
        
        #region Constructors

        public Filter()
        {
            Ands = new List<Filter<T>>();
            Ors = new List<Filter<T>>();
        }
        
        //public Filter(object expression) : base()
        //{
        //	Ands = new List<Filter<T>>();
        //	Ors = new List<Filter<T>>();
        //}

        public Filter(Expression<Func<T, object?>> expression) : base(expression)
        {
            Ands = new List<Filter<T>>();
            Ors = new List<Filter<T>>();
        }

        public Filter(string property) : base(property)
        {
            Ands = new List<Filter<T>>();
            Ors = new List<Filter<T>>();
        }

        public Filter(string property, Operator op, object value) : base(property)
        {
            Ands = new List<Filter<T>>();
            Ors = new List<Filter<T>>();
            Operator = op;
            Value = value;
        }

        public Filter(SerializationInfo info, StreamingContext context)
        {
            Ands = new List<Filter<T>>();
            Ors = new List<Filter<T>>();
            Deserialize(info, context);
        }

        public static explicit operator Filter<T>?(Filter<Entity>? v)
        {
            if (v == null)
                return null;

            //Filter<T> result = new Filter<T>();
            var json = Serialization.Serialize(v);
            json = json.Replace(typeof(Entity).EntityName(), typeof(T).EntityName());
            var result = Serialization.Deserialize<Filter<T>>(json);
            //IFormatterConverter formatter = new System.Runtime.Serialization.FormatterConverter()
            //SerializationInfo info = new SerializationInfo(typeof(T), );
            //v.Serialize(info, new StreamingContext());
            //result.Deserialize(info, new StreamingContext());
            return result;
        }

        #endregion

        #region IsEqualTo

        public Filter<T> IsEqualTo(bool value) => ApplyOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(bool value) => IsEqualTo(value);
        
        public Filter<T> IsEqualTo(string value) => ApplyStringOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(string value) => IsEqualTo(value);
        
        public Filter<T> IsEqualTo(Guid value) => ApplyOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(Guid value) => IsEqualTo(value);
        
        public Filter<T> IsEqualTo(int value) => ApplyOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(int value) => IsEqualTo(value);
        
        public Filter<T> IsEqualTo(DateTime value) =>  ApplyOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(DateTime value) => IsEqualTo(value);

        public Filter<T> IsEqualTo(TimeSpan value) => ApplyOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(TimeSpan value) => IsEqualTo(value);
        
        public Filter<T> IsEqualTo<TEnum>(TEnum value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo<TEnum>(TEnum value) 
            where TEnum : struct 
            => IsEqualTo<TEnum>(value);
        
        public Filter<T> IsEqualTo(object value) => ApplyObjectOperator(Operator.IsEqualTo, value);
        IFilter IFilter.IsEqualTo(object value) => IsEqualTo(value);

        #endregion

        #region IsNotEqualTo

        public Filter<T> IsNotEqualTo(bool value) =>  ApplyOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(bool value) => IsNotEqualTo(value);
        
        public Filter<T> IsNotEqualTo(string value) => ApplyStringOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(string value) => IsNotEqualTo(value);
        
        public Filter<T> IsNotEqualTo(Guid value) => ApplyOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(Guid value) => IsNotEqualTo(value);
        
        public Filter<T> IsNotEqualTo(int value) => ApplyOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(int value) => IsNotEqualTo(value);
        
        public Filter<T> IsNotEqualTo(DateTime value) =>  ApplyOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(DateTime value) => IsNotEqualTo(value);

        public Filter<T> IsNotEqualTo(TimeSpan value) => ApplyOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(TimeSpan value) => IsNotEqualTo(value);
        
        public Filter<T> IsNotEqualTo<TEnum>(TEnum value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo<TEnum>(TEnum value) 
            where TEnum : struct 
            => IsNotEqualTo<TEnum>(value);
        
        public Filter<T> IsNotEqualTo(object value) => ApplyObjectOperator(Operator.IsNotEqualTo, value);
        IFilter IFilter.IsNotEqualTo(object value) => IsNotEqualTo(value);
        
        #endregion

        #region IsGreaterThan

        public Filter<T> IsGreaterThan(bool value) =>  ApplyOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(bool value) => IsGreaterThan(value);
        
        public Filter<T> IsGreaterThan(string value) => ApplyStringOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(string value) => IsGreaterThan(value);
        
        public Filter<T> IsGreaterThan(Guid value) => ApplyOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(Guid value) => IsGreaterThan(value);
        
        public Filter<T> IsGreaterThan(int value) => ApplyOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(int value) => IsGreaterThan(value);
        
        public Filter<T> IsGreaterThan(DateTime value) =>  ApplyOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(DateTime value) => IsGreaterThan(value);

        public Filter<T> IsGreaterThan(TimeSpan value) => ApplyOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(TimeSpan value) => IsGreaterThan(value);
        
        public Filter<T> IsGreaterThan<TEnum>(TEnum value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan<TEnum>(TEnum value) 
            where TEnum : struct 
            => IsGreaterThan<TEnum>(value);
        
        public Filter<T> IsGreaterThan(object value) => ApplyObjectOperator(Operator.IsGreaterThan, value);
        IFilter IFilter.IsGreaterThan(object value) => IsGreaterThan(value);

        #endregion

        #region IsGreaterThanOrEqualTo
        
        public Filter<T> IsGreaterThanOrEqualTo(bool value) =>  ApplyOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(bool value) => IsGreaterThanOrEqualTo(value);
        
        public Filter<T> IsGreaterThanOrEqualTo(string value) => ApplyStringOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(string value) => IsGreaterThanOrEqualTo(value);
        
        public Filter<T> IsGreaterThanOrEqualTo(Guid value) => ApplyOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(Guid value) => IsGreaterThanOrEqualTo(value);
        
        public Filter<T> IsGreaterThanOrEqualTo(int value) => ApplyOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(int value) => IsGreaterThanOrEqualTo(value);
        
        public Filter<T> IsGreaterThanOrEqualTo(DateTime value) =>  ApplyOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(DateTime value) => IsGreaterThanOrEqualTo(value);

        public Filter<T> IsGreaterThanOrEqualTo(TimeSpan value) => ApplyOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(TimeSpan value) => IsGreaterThanOrEqualTo(value);
        
        public Filter<T> IsGreaterThanOrEqualTo<TEnum>(TEnum value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo<TEnum>(TEnum value) 
            where TEnum : struct 
            => IsGreaterThanOrEqualTo<TEnum>(value);
        
        public Filter<T> IsGreaterThanOrEqualTo(object value) => ApplyObjectOperator(Operator.IsGreaterThanOrEqualTo, value);
        IFilter IFilter.IsGreaterThanOrEqualTo(object value) => IsGreaterThanOrEqualTo(value);

        #endregion

        #region IsLessThan

        public Filter<T> IsLessThan(bool value) =>  ApplyOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(bool value) => IsLessThan(value);
        
        public Filter<T> IsLessThan(string value) => ApplyStringOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(string value) => IsLessThan(value);
        
        public Filter<T> IsLessThan(Guid value) => ApplyOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(Guid value) => IsLessThan(value);
        
        public Filter<T> IsLessThan(int value) => ApplyOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(int value) => IsLessThan(value);
        
        public Filter<T> IsLessThan(DateTime value) =>  ApplyOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(DateTime value) => IsLessThan(value);

        public Filter<T> IsLessThan(TimeSpan value) => ApplyOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(TimeSpan value) => IsLessThan(value);
        
        public Filter<T> IsLessThan<TEnum>(TEnum value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan<TEnum>(TEnum value) 
            where TEnum : struct 
            => IsLessThan<TEnum>(value);
        
        public Filter<T> IsLessThan(object value) => ApplyObjectOperator(Operator.IsLessThan, value);
        IFilter IFilter.IsLessThan(object value) => IsLessThan(value);

        #endregion

        #region IsLessThanOrEqualTo
        
        public Filter<T> IsLessThanOrEqualTo(bool value) =>  ApplyOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(bool value) => IsLessThanOrEqualTo(value);
        
        public Filter<T> IsLessThanOrEqualTo(string value) => ApplyStringOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(string value) => IsLessThanOrEqualTo(value);
        
        public Filter<T> IsLessThanOrEqualTo(Guid value) => ApplyOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(Guid value) => IsLessThanOrEqualTo(value);
        
        public Filter<T> IsLessThanOrEqualTo(int value) => ApplyOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(int value) => IsLessThanOrEqualTo(value);
        
        public Filter<T> IsLessThanOrEqualTo(DateTime value) =>  ApplyOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(DateTime value) => IsLessThanOrEqualTo(value);

        public Filter<T> IsLessThanOrEqualTo(TimeSpan value) => ApplyOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(TimeSpan value) => IsLessThanOrEqualTo(value);
        
        public Filter<T> IsLessThanOrEqualTo<TEnum>(TEnum value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo<TEnum>(TEnum value) 
            where TEnum : struct 
            => IsLessThanOrEqualTo<TEnum>(value);
        
        public Filter<T> IsLessThanOrEqualTo(object value) => ApplyObjectOperator(Operator.IsLessThanOrEqualTo, value);
        IFilter IFilter.IsLessThanOrEqualTo(object value) => IsLessThanOrEqualTo(value);
        
        #endregion

        #region BeginsWith

        public Filter<T> BeginsWith(string value) => ApplyStringOperator(Operator.BeginsWith, value);
        IFilter IFilter.BeginsWith(string value) => BeginsWith(value);
        
        public Filter<T> BeginsWith(object value) => ApplyObjectOperator(Operator.BeginsWith, value);
        IFilter IFilter.BeginsWith(object value) => BeginsWith(value);
        
        #endregion

        #region EndsWith

        public Filter<T> EndsWith(string value) => ApplyStringOperator(Operator.EndsWith, value);
        IFilter IFilter.EndsWith(string value) => EndsWith(value);
        
        public Filter<T> EndsWith(object value) => ApplyObjectOperator(Operator.EndsWith, value);
        IFilter IFilter.EndsWith(object value) => EndsWith(value);

        #endregion

        #region Contains

        public Filter<T> Contains(string value) => ApplyStringOperator(Operator.Contains, value);
        IFilter IFilter.Contains(string value) => Contains(value);
        
        public Filter<T> Contains(object value) => ApplyObjectOperator(Operator.Contains, value);
        IFilter IFilter.Contains(object value) => Contains(value);

        #endregion

        #region InList

        public Filter<T> InList(params bool[] value) =>  ApplyOperator(Operator.InList, value);
        IFilter IFilter.InList(params bool[] value) => InList(value);

        public Filter<T> InList(params string[] value) => ApplyStringOperator(Operator.InList, value);
        IFilter IFilter.InList(params string[] value) => InList(value);

        public Filter<T> InList(params Guid[] value) => ApplyOperator(Operator.InList, value);
        IFilter IFilter.InList(params Guid[] value) => InList(value);

        public Filter<T> InList(params int[] value) => ApplyOperator(Operator.InList, value);
        IFilter IFilter.InList(params int[] value) => InList(value);

        public Filter<T> InList(params DateTime[] value) => ApplyOperator(Operator.InList, value);
        IFilter IFilter.InList(params DateTime[] value) => InList(value);

        public Filter<T> InList(params TimeSpan[] value) => ApplyOperator(Operator.InList, value);
        IFilter IFilter.InList(params TimeSpan[] value) => InList(value);

        public Filter<T> InList<TEnum>(params TEnum[] value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.InList, value);
        IFilter IFilter.InList<TEnum>(params TEnum[] value) => InList(value);

        public Filter<T> InList(params object[] value) => ApplyObjectOperator(Operator.InList, value);
        IFilter IFilter.InList(params object[] value) => InList(value);

        #endregion

        #region NotInList

        public Filter<T> NotInList(params bool[] value) =>  ApplyOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params bool[] value) => NotInList(value);

        public Filter<T> NotInList(params string[] value) => ApplyStringOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params string[] value) => NotInList(value);

        public Filter<T> NotInList(params Guid[] value) => ApplyOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params Guid[] value) => NotInList(value);

        public Filter<T> NotInList(params int[] value) => ApplyOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params int[] value) => InList(value);

        public Filter<T> NotInList(params DateTime[] value) => ApplyOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params DateTime[] value) => NotInList(value);

        public Filter<T> NotInList(params TimeSpan[] value) => ApplyOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params TimeSpan[] value) => NotInList(value);

        public Filter<T> NotInList<TEnum>(params TEnum[] value) 
            where TEnum : struct, IConvertible, IComparable, IFormattable
            => ApplyEnumOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList<TEnum>(params TEnum[] value) => NotInList(value);

        public Filter<T> NotInList(params object[] value) => ApplyObjectOperator(Operator.NotInList, value);
        IFilter IFilter.NotInList(params object[] value) => InList(value);

        #endregion

        #region InQuery

        public Filter<T> InQuery<U>(SubQuery<U> value) => ApplyObjectOperator(Operator.InQuery, value);
        IFilter IFilter.InQuery<U>(SubQuery<U> value) => InQuery(value);

        public Filter<T> InQuery<U>(Filter<U> filter, Expression<Func<U, object?>> column) => InQuery(new SubQuery<U>(filter, new Column<U>(column)));
        IFilter IFilter.InQuery<U>(Filter<U> value, Expression<Func<U, object?>> column) => InQuery(value, column);

        #endregion

        #region All/None

        IFilter IFilter.All() => All();
        public Filter<T> All() => ApplyNullOperator(Operator.All);

        IFilter IFilter.None() => None();
        public Filter<T> None() => ApplyNullOperator(Operator.None);

        #endregion

        #region Lists of Values

        /// <summary>
        /// Create a new filter by aggregating comparisons of <paramref name="expression"/> to <paramref name="values"/>
        /// into a list of Ands or Ors, depending on <paramref name="includes"/>.
        /// </summary>
        /// <remarks>
        /// Returns <see langword="null"/> if <paramref name="values"/> is empty.
        /// </remarks>
        /// <typeparam name="TType"></typeparam>
        /// <param name="expression"></param>
        /// <param name="includes"></param>
        /// <param name="values"></param>
        /// <returns>
        /// <see langword="null"/> if <paramref name="values"/> is empty, otherwise a new filter.
        /// </returns>
        private static Filter<T>? CreateListFilter<TType>(Expression<Func<T, TType>> expression, ListOperator includes, IEnumerable<TType> values)
        {
            Filter<T>? result = null;
            foreach (var value in values)
            {
                CoreUtils.TryFindMemberExpression(expression.Body, out var me);

                var filter = new Filter<T>();
                filter.Expression = me;
                filter.Operator = includes.Equals(ListOperator.Includes) ? Operator.IsEqualTo : Operator.IsNotEqualTo;
                filter.Value = value;

                if (result == null)
                {
                    result = filter;
                }
                else
                {
                    if (includes.Equals(ListOperator.Includes))
                        result.Ors.Add(filter);
                    else
                        result.Ands.Add(filter);
                }
            }

            return result;
        }

        public static Filter<T>? List(Expression<Func<T, bool>> expression, ListOperator includes, IEnumerable<bool> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        public static Filter<T>? List(Expression<Func<T, string>> expression, ListOperator includes, IEnumerable<string> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        public static Filter<T>? List(Expression<Func<T, Guid>> expression, ListOperator includes, IEnumerable<Guid> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        public static Filter<T>? List(Expression<Func<T, int>> expression, ListOperator includes, IEnumerable<int> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        public static Filter<T>? List(Expression<Func<T, DateTime>> expression, ListOperator includes, IEnumerable<DateTime> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        public static Filter<T>? List(Expression<Func<T, TimeSpan>> expression, ListOperator includes, IEnumerable<TimeSpan> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        public static Filter<T>? List(Expression<Func<T, object>> expression, ListOperator includes, IEnumerable<object> values)
        {
            return CreateListFilter(expression, includes, values);
        }

        public static Filter<T>? List<TEnum>(Expression<Func<T, TEnum>> expression, ListOperator includes, IEnumerable<TEnum> values)
        {
            return CreateListFilter(expression, includes, values);
            //.Cast<object>());
        }

        #endregion

        #region Expression Functions

        public Expression<Func<T, bool>> AsExpression()
        {
            var expression = Expression;
            if (expression is null)
                throw new Exception("Expression is null");

            var param = Expression.Parameter(typeof(T), "x");
            //Expression expr = param;

            Expression constant = Expression.Constant(null, typeof(object));

            //expr = Expression;

            if (Value != null)
            {
                var valType = Value.GetType();

                var propType = expression.Type;

                if (valType.Equals(typeof(string)) && propType.Equals(typeof(Guid)))
                    constant = Expression.Constant(Guid.Parse(Value.ToString()), propType);
                else if (valType.Equals(typeof(string)) && propType.Equals(typeof(bool)))
                    constant = Expression.Constant(bool.Parse(Value.ToString()), propType);
                else if (valType.Equals(typeof(string)) && propType.Equals(typeof(TimeSpan)))
                    constant = Expression.Constant(TimeSpan.Parse(Value.ToString()), propType);
                else if (valType.Equals(typeof(string)) && propType.Equals(typeof(ScriptType)))
                    constant = Expression.Constant((ScriptType)int.Parse(Value.ToString()), propType);
                //else if (valType.Equals(typeof(String)) && propType.Equals(typeof(DateTime)))
                //    constant = Expression.Constant(JsonConvert.DeserializeObject<DateTime>(Value.ToString()), propType);

                else
                    try
                    {
                        constant = Expression.Constant(Value, expression.Type);
                        //constant = Expression.Constant(Value, propType);
                    }
                    catch
                    {
                        var val = Value;

                        if (!propType.Equals(valType) && !(val is FilterConstant))
                        {
                            val = CoreUtils.ChangeType(val, propType);
                        }
                        constant = Expression.Constant(val, propType);
                    }
            }

            Expression final = Operator switch
            {
                Operator.IsEqualTo => Expression.Equal(expression, constant),
                Operator.IsNotEqualTo => Expression.NotEqual(expression, constant),
                Operator.IsGreaterThan => Expression.GreaterThan(expression, constant),
                Operator.IsGreaterThanOrEqualTo => Expression.GreaterThanOrEqual(expression, constant),
                Operator.IsLessThan => Expression.LessThan(expression, constant),
                Operator.IsLessThanOrEqualTo => Expression.LessThanOrEqual(expression, constant),
                Operator.Contains => CreateContainsExpression(expression, constant),
                _ => throw new Exception("Invalid Operator"),
            };
            var result = Expression.Lambda<Func<T, bool>>(final, param);
            if (Ands != null)
                foreach (var filter in Ands)
                    result = result.And(filter.AsExpression());
            if (Ors != null)
                foreach (var filter in Ors)
                    result = result.Or(filter.AsExpression());

            return result;
        }

        private Expression CreateContainsExpression(Expression expr, Expression constant)
        {
            var parameterExp = Expression.Parameter(typeof(string), "type");
            var method = typeof(string).GetTypeInfo().GetMethod("Contains", new[] { typeof(string) });
            var containsMethodExp = Expression.Call(expr, method, constant);
            return containsMethodExp;
        }

        #endregion

        #region Serialization

        public override void Serialize(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Operator", Operator.ToString());
            info.AddValue("Value", Value);
            if (Ands.Count > 0)
                info.AddValue("Ands", Ands, typeof(List<Filter<T>>));
            if (Ors.Count > 0)
                info.AddValue("Ors", Ors, typeof(List<Filter<T>>));
        }

        public override void Deserialize(SerializationInfo info, StreamingContext context)
        {
            //Expression = CoreUtils.StringToExpression(typeof(T), (String)info.GetValue("Expression", typeof(String)));

            Operator = (Operator)Enum.Parse(typeof(Operator), (string)info.GetValue("Operator", typeof(string)));
            Value = info.GetValue("Value", typeof(object));

            try
            {
                Ands = (List<Filter<T>>)info.GetValue("Ands", typeof(List<Filter<T>>));
            }
            catch
            {
                Ands = new List<Filter<T>>();
            }

            try
            {
                Ors = (List<Filter<T>>)info.GetValue("Ors", typeof(List<Filter<T>>));
            }
            catch
            {
                Ors = new List<Filter<T>>();
            }
        }

        #endregion
        
        #region Display Functions

        private static string ValueToString(object? value)
        {
            if (value is string str)
                return "'" + str + "'";
            else if (value is Guid guid)
                return "guid'" + value + "'";
            else if (value is DateTime date)
                return "'" + date.ToString("o") + "'";
            else if (value is IList list)
                return $"[{string.Join(',', list.Cast<object?>().Select(x => ValueToString(x)))}]";
            else
                return value?.ToString() ?? "''";
        }

        public string AsOData()
        {
            var operators = new Dictionary<Operator, string>
            {
                { Operator.IsEqualTo, "{0} eq {1}" },
                { Operator.IsNotEqualTo, "{0} ne {1}" },
                { Operator.IsGreaterThan, "{0} gt {1}" },
                { Operator.IsGreaterThanOrEqualTo, "{0} ge {1}" },
                { Operator.IsLessThan, "{0} lt {1}" },
                { Operator.IsLessThanOrEqualTo, "{0} le {1}" },
                { Operator.BeginsWith, "startswith({0},{1}) eq true" },
                { Operator.Contains, "substringof({1} {0}) eq true" },
                { Operator.InList, "{0} in ({1})" },
                { Operator.NotInList, "{0} not in ({1})" },
                { Operator.InQuery, "{0} in ({1})" }
            };

            var result = "";
            if(Operator == Operator.All)
            {
                result = "All";
            }
            else if(Operator == Operator.None)
            {
                result = "None";
            }
            else if (Expression != null)
            {
                var value = "''";
                if (Value != null)
                {
                    value = ValueToString(Value);
                }

                string prop;
                if (CoreUtils.TryFindMemberExpression(Expression, out var mexp))
                    prop = CoreUtils.GetFullPropertyName(mexp, "/");
                else
                    prop = Expression.ToString();


                result = string.Format("(" + operators[Operator] + ")",
                    prop,
                    value
                );
            }

            if (Ands != null && Ands.Count > 0)
                foreach (var and in Ands)
                {
                    var AndResult = and.AsOData();
                    if (!string.IsNullOrEmpty(AndResult))
                        result = string.Format("{0} and {1}", result, AndResult);
                }

            var bChanged = false;
            if (Ors != null && Ors.Count > 0)
            {
                foreach (var or in Ors)
                {
                    var OrResult = or.AsOData();
                    if (!string.IsNullOrEmpty(OrResult))
                    {
                        result = string.Format("{0} or {1}", result, OrResult);
                        bChanged = true;
                    }
                }

                if (bChanged)
                    result = string.Format("({0})", result);
            }

            return result;
        }

        public IEnumerable<string> ColumnNames()
        {
            List<String> result = new List<string>();
            result.Add(CoreUtils.ExpressionToString(typeof(T), Expression));
            foreach (var and in Ands)
                result.AddRange(and.ColumnNames());
            foreach (var or in Ors)
                result.AddRange(or.ColumnNames());
            return result;
        }

        public override string ToString()
        {
            return AsOData();
        }

        #endregion

    }

    public class Filters<T> //where T : Entity
    {
        private readonly List<Filter<T>> filters = new List<Filter<T>>();

        public Filters(params Expression<Func<T, object>>[] expressions)
        {
            foreach (var expression in expressions)
                filters.Add(new Filter<T>(expression!));
        }

        public Filter<T>[] Items
        {
            get => filters.ToArray();
            set
            {
                filters.Clear();
                filters.AddRange(value);
            }
        }

        public void Add(Filter<T>? filter)
        {
            if(filter != null)
                filters.Add(filter);
        }

        public void AddRange(IEnumerable<Filter<T>> criteria)
        {
            filters.AddRange(criteria);
        }

        public void Remove(string column)
        {
            //Filter<T> filter = new Filter<T>(column);
            //columns.RemoveAll(x => x.ToString() == filter.ToString());
        }

        public static explicit operator Filters<T>(Filters<Entity> vs)
        {
            var result = new Filters<T>();
            var filter = new List<Filter<T>>();
            foreach (var v in vs.Items)
            {
                var cast = (Filter<T>?)v;
                if(cast != null)
                    filter.Add(cast);
            }
            result.Items = filter.ToArray();
            return result;
        }

        //public Expression<Func<T,bool>> AsExpression()
        //{
        //	Expression<Func<T, bool>> result = null;
        //	foreach (Filter<T> filter in filters)
        //	{
        //		if (result == null)
        //			result = filter.AsExpression();
        //		else
        //			result = result.And(filter.AsExpression());
        //	}
        //	return result;
        //}

        public Filter<T>? Combine()
        {
            Filter<T>? result = null;
            foreach (var filter in filters)
                if (result is null)
                    result = filter;
                else
                    result.Ands.Add(filter);
            return result;
        }
    }

    public class FilterJsonConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        {
            if (!(value is IFilter filter))
            {
                writer.WriteNull();
                return;
            }

            var property = filter.Expression;
            //MethodInfo mi = value.GetType().GetTypeInfo().GetMethod("ExpressionToString");
            //String prop = mi.Invoke(value, new object[] { property, true }) as String;
            var prop = CoreUtils.ExpressionToString(value.GetType().GenericTypeArguments[0], property, true);

            var op = filter.Operator;
            var val = filter.Value;

            var ands = filter.Ands.ToList();
            var ors = filter.Ors.ToList();

            //var table = value as DataTable;
            //// Save Columns
            writer.WriteStartObject();

            writer.WritePropertyName("Expression");
            writer.WriteValue(prop);

            writer.WritePropertyName("Operator");
            writer.WriteValue(op);

            if (val is FilterConstant)
            {
                writer.WritePropertyName("FilterConstant");
                writer.WriteValue(val);
            }
            else
            {
                writer.WritePropertyName("Value");
                var valType = val == null ? property.Type : val.GetType();
                if (valType.IsArray)
                {
                    writer.WriteStartArray();
                    var arr = (val as IEnumerable)!;
                    foreach (var v in arr)
                        writer.WriteValue(v);
                    writer.WriteEndArray();
                }
                else if (valType.IsConstructedGenericType && valType.GetGenericTypeDefinition() == typeof(SubQuery<>))
                {
                    var serialized = Serialization.Serialize(val);
                    var wrapper = new SubQuerySerializationWrapper(valType.GenericTypeArguments[0].EntityName(), serialized);

                    var str = Serialization.Serialize(wrapper);

                    writer.WriteValue(str);
                }
                else
                {
                    writer.WriteValue(val);
                }
            }

            if (ands != null && ands.Count > 0)
            {
                writer.WritePropertyName("Ands");
                serializer.Serialize(writer, ands);
            }

            if (ors != null && ors.Count > 0)
            {
                writer.WritePropertyName("Ors");
                serializer.Serialize(writer, ors);
            }

            writer.WriteEndObject();
        }

        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;

            var data = new Dictionary<string, object>();

            while (reader.TokenType != JsonToken.EndObject && reader.Read())
                if (reader.Value != null)
                {
                    var key = reader.Value.ToString();
                    reader.Read();
                    if (string.Equals(key, "Ors"))
                    {
                        var ors = new List<object>();
                        var array = JArray.Load(reader);
                        foreach (var item in array)
                        {
                            var or = ReadJson(item.CreateReader(), objectType, existingValue, serializer);
                            if(or != null)
                                ors.Add(or);
                        }

                        data[key] = ors;
                    }
                    else if (string.Equals(key, "Ands"))
                    {
                        var ands = new List<object>();
                        var array = JArray.Load(reader);
                        foreach (var item in array)
                        {
                            var and = ReadJson(item.CreateReader(), objectType, existingValue, serializer);
                            if(and != null)
                                ands.Add(and);
                        }

                        data[key] = ands;
                    }
                    else if (reader.TokenType == JsonToken.StartArray)
                    {
                        var vals = new List<object>();
                        while (reader.TokenType != JsonToken.EndArray && reader.Read())
                            if (reader.Value != null)
                                vals.Add(reader.Value);
                        data[key] = vals;
                    }
                    else
                    {
                        data[key] = reader.Value;
                    }
                }

            var result = (Activator.CreateInstance(objectType) as IFilter)!;

            var prop = data["Expression"].ToString();
            var exp = CoreUtils.StringToExpression(prop);
            var op = (Operator)int.Parse(data["Operator"].ToString());

            result.Expression = exp;
            result.Operator = op;
            if(data.TryGetValue("Value", out var val))
            {
                if (op == Operator.InQuery)
                {
                    var wrapper = Serialization.Deserialize<SubQuerySerializationWrapper>((string)val);
                    val = Activator.CreateInstance(typeof(SubQuery<>).MakeGenericType(CoreUtils.GetEntity(wrapper.Type)), null, null);
                    Serialization.DeserializeInto(wrapper.SerializedSubquery, val);
                }
                result.Value = CoreUtils.ChangeType(val, exp.Type);
            }
            else if(data.TryGetValue("FilterConstant", out var filterConstant))
            {
                result.Value = CoreUtils.ChangeType<FilterConstant>(filterConstant);
            }

            if (data.ContainsKey("Ors"))
            {
                var source = (data["Ors"] as List<object>)!;
                foreach (var srcitem in source)
                    result.Or((srcitem as IFilter)!);
            }

            if (data.ContainsKey("Ands"))
            {
                var source = (data["Ands"] as List<object>)!;
                foreach (var srcitem in source)
                    result.And((srcitem as IFilter)!);
            }

            return result;
        }

        public override bool CanConvert(Type objectType)
        {
            if (objectType.IsConstructedGenericType)
            {
                var ot = objectType.GetGenericTypeDefinition();
                var tt = typeof(Filter<>);
                if (ot == tt)
                    return true;
            }

            return false;
        }
    }
}