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

namespace InABox.Core
{
    public enum SortDirection
    {
        Ascending,
        Descending
    }

    public interface ISortOrder : ISerializeBinary
    {
        SortDirection Direction { get; set; }

        Expression Expression { get; set; }

        IEnumerable<ISortOrder> Thens { get; }

        IEnumerable<String> ColumnNames();

        string AsOData();

        void SerializeBinary(CoreBinaryWriter writer);
        void DeserializeBinary(CoreBinaryReader reader);
    }

    public static class SortOrder
    {
        public static ISortOrder Create<T>(Type concrete, Expression<Func<T,object>> expression, SortDirection direction = SortDirection.Ascending)
        {
            if (!typeof(T).IsAssignableFrom(concrete))
                throw new Exception($"Columns: {concrete.EntityName()} does not implement {typeof(T).EntityName()}");
            var type = typeof(SortOrder<>).MakeGenericType(concrete);
            var property = CoreUtils.GetFullPropertyName(expression,".");
            var result = Activator.CreateInstance(type, property, direction );
            return (result as ISortOrder)!;
        }
    }
    
    public class SortOrder<T> : SerializableExpression<T>, ISortOrder // where T : Entity
    {
        public SortDirection Direction { get; set; }

        public List<SortOrder<T>> Thens { get; private set; }

        IEnumerable<ISortOrder> ISortOrder.Thens => Thens;

        //public SortOrder<T> Ascending()
        //{
        //	Direction = SortOrder.Ascending;
        //	return this;
        //}

        //public SortOrder<T> Descending()
        //{
        //	Direction = SortOrder.Descending;
        //	return this;
        //}

        public SortOrder<T> ThenBy(Expression<Func<T, object?>> expression, SortDirection direction = SortDirection.Ascending)
        {
            var thenby = new SortOrder<T>(expression, direction);
            Thens.Add(thenby);
            return this;
        }

        #region Constructors

        public SortOrder()
        {
            Thens = new List<SortOrder<T>>();
            Direction = SortDirection.Ascending;
        }

        public SortOrder(Expression<Func<T, object?>> expression, SortDirection direction = SortDirection.Ascending)
            : base(expression)
        {
            Thens = new List<SortOrder<T>>();
            Direction = direction;
        }

        public SortOrder(string property, SortDirection direction = SortDirection.Ascending)
        {
            Thens = new List<SortOrder<T>>();
            Direction = direction;
            var iprop = DatabaseSchema.Property(typeof(T), property);
            Expression = iprop.Expression();
        }

        public SortOrder(SerializationInfo info, StreamingContext context)
        {
            Deserialize(info, context);
        }

        public static explicit operator SortOrder<T>(SortOrder<Entity> v)
        {
            if (v == null)
                return null;
            var json = Serialization.Serialize(v);
            json = json.Replace(typeof(Entity).EntityName(), typeof(T).EntityName());
            var result = Serialization.Deserialize<SortOrder<T>>(json);
            return result;
        }

        #endregion

        #region Display Functions

        public string AsOData()
        {
            var orderby = new Dictionary<SortDirection, string>
            {
                { SortDirection.Ascending, "asc" },
                { SortDirection.Descending, "desc" }
            };

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

            var result = string.Format("{0} {1}", prop, orderby[Direction]);

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

            return result;
        }

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

        public IEnumerable<string> ColumnNames()
        {
            yield return CoreUtils.ExpressionToString(typeof(T), Expression);
            foreach (var then in Thens)
                foreach (var x in then.ColumnNames())
                    yield return x;
        }

        #endregion

        //public Expression<Func<T,Object>> AsExpression()
        //{
        //          var param = Expression.Parameter(typeof(T), "x");
        //          var result = Expression.Lambda<Func<T,Object>>(Expression,param);
        //	return result;
        //}

        #region Serialization

        public override void Serialize(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Direction", Direction.ToString());
            if (Thens.Count > 0)
                info.AddValue("Thens", Thens, typeof(List<SortOrder<T>>));
        }

        public override void Deserialize(SerializationInfo info, StreamingContext context)
        {
            Direction = (SortDirection)Enum.Parse(typeof(SortDirection), (string)info.GetValue("Direction", typeof(string)));
            try
            {
                Thens = (List<SortOrder<T>>)info.GetValue("Thens", typeof(List<SortOrder<T>>));
            }
            catch
            {
                Thens = new List<SortOrder<T>>();
            }
        }

        #endregion

        #region Binary Serialization

        public void SerializeBinary(CoreBinaryWriter writer)
        {
            writer.SerialiseExpression(typeof(T), Expression, false);
            writer.Write((byte)Direction);
            writer.Write(Thens.Count);
            foreach (var then in Thens)
            {
                then.SerializeBinary(writer);
            }
        }

        public void DeserializeBinary(CoreBinaryReader reader)
        {
            Expression = reader.DeserialiseExpression(typeof(T));
            Direction = (SortDirection)reader.ReadByte();

            Thens.Clear();
            var nThens = reader.ReadInt32();
            for(int i = 0; i < nThens; ++i)
            {
                var then = new SortOrder<T>();
                then.DeserializeBinary(reader);
                Thens.Add(then);
            }
        }

        #endregion
    }
    public static class SortOrderSerialization
    {
        /// <summary>
        /// Inverse of <see cref="Write{T}(CoreBinaryWriter, SortOrder{T}?)"/>.
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static SortOrder<T>? ReadSortOrder<T>(this CoreBinaryReader reader)
        {
            if (reader.ReadBoolean())
            {
                var sortOrder = new SortOrder<T>();
                sortOrder.DeserializeBinary(reader);
                return sortOrder;
            }
            return null;
        }

        /// <summary>
        /// Inverse of <see cref="ReadSortOrder{T}(CoreBinaryReader)"/>.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="writer"></param>
        public static void Write<T>(this CoreBinaryWriter writer, SortOrder<T>? sortOrder)
        {
            if (sortOrder is null)
            {
                writer.Write(false);
            }
            else
            {
                writer.Write(true);
                sortOrder.SerializeBinary(writer);
            }
        }
    }

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

            var property = CoreUtils.GetPropertyValue(value, "Expression") as MemberExpression;
            //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 dir = CoreUtils.GetPropertyValue(value, "Direction");

            writer.WriteStartObject();
            
            writer.WritePropertyName("$type");
            writer.WriteValue(value.GetType().FullName);

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

            writer.WritePropertyName("Direction");
            writer.WriteValue(dir);

            var thens = CoreUtils.GetPropertyValue(value, "Thens") as IList;
            if (thens != null && thens.Count > 0)
            {
                writer.WritePropertyName("Thens");
                serializer.Serialize(writer, thens);
            }

            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, "$type"))
                        objectType = Type.GetType(reader.Value.ToString()) ?? objectType;
                    else if (string.Equals(key, "Thens"))
                    {
                        var array = JArray.Load(reader);

                        var thens = new List<object>();
                        foreach (var item in array)
                        {
                            var then = ReadJson(item.CreateReader(), objectType, existingValue, serializer);
                            if(then != null)
                                thens.Add(then);
                            //String jexp = item["Expression"].Value<String>();
                            //MemberExpression exp = CoreUtils.StringToExpression(jexp) as MemberExpression;
                            //var then = CreateSortOrder(
                            //    objectType,
                            //    exp.Member.Name,
                            //    (SortDirection)item["Direction"].Value<Int64>()
                            //);
                            //thens.Add(then);
                        }

                        data[key] = thens;
                    }
                    else
                    {
                        data[key] = reader.Value;
                    }
                }

            var jprop = data["Expression"].ToString();
            var prop = CoreUtils.StringToExpression(jprop) as MemberExpression;
            var direction = (SortDirection)int.Parse(data["Direction"].ToString());

            var result = Activator.CreateInstance(objectType, CoreUtils.GetFullPropertyName(prop, "."), direction);

            if (data.ContainsKey("Thens"))
            {
                var source = (data["Thens"] as List<object>)!;
                var target = (CoreUtils.GetPropertyValue(result, "Thens") as IList)!;
                foreach (var srcitem in source)
                    target.Add(srcitem);
            }

            return result;
        }

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

            return false;
        }
    }
}