using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using InABox.Clients;
using JetBrains.Annotations;
using Newtonsoft.Json;

namespace InABox.Core
{
    public interface ISerializeBinary
    {
        public void SerializeBinary(BinaryWriter writer);

        public void DeserializeBinary(BinaryReader reader);
    }

    public static class Serialization
    {
        private static JsonSerializerSettings? _serializerSettings;

        private static JsonSerializerSettings SerializerSettings(bool indented = true)
        {
            if (_serializerSettings == null)
            {
                _serializerSettings = new JsonSerializerSettings
                {
                    DateParseHandling = DateParseHandling.DateTime,
                    DateFormatHandling = DateFormatHandling.IsoDateFormat,
                    DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind
                };

                _serializerSettings.Converters.Add(new DataTableJsonConverter());
                //serializerSettings.Converters.Add(new DateTimeJsonConverter());

                _serializerSettings.Converters.Add(new FilterJsonConverter());
                _serializerSettings.Converters.Add(new ColumnJsonConverter());
                _serializerSettings.Converters.Add(new SortOrderJsonConverter());
                _serializerSettings.Converters.Add(new UserPropertiesJsonConverter());
            }
            _serializerSettings.Formatting = indented ? Formatting.Indented : Formatting.None;

            return _serializerSettings;
        }

        public static string Serialize(object? o, bool indented = false)
        {
            var json = JsonConvert.SerializeObject(o, SerializerSettings(indented));
            return json;
        }

        public static void Serialize(object o, Stream stream, bool indented = false)
        {
            var settings = SerializerSettings(indented);
            using (var sw = new StreamWriter(stream))
            {
                using (JsonWriter writer = new JsonTextWriter(sw))
                {
                    var serializer = JsonSerializer.Create(settings);
                    serializer.Serialize(writer, o);
                }
            }
        }

        public static void DeserializeInto(string json, object target)
        {
            JsonConvert.PopulateObject(json, target, SerializerSettings());
        }

        [return: MaybeNull]
        public static T Deserialize<T>(Stream? stream, bool strict = false)
        {
            if (stream == null)
                return default;

            try
            {
                var settings = SerializerSettings();
                using var sr = new StreamReader(stream);
                using JsonReader reader = new JsonTextReader(sr);
                var serializer = JsonSerializer.Create(settings);
                return serializer.Deserialize<T>(reader);
            }
            catch (Exception e)
            {
                if (strict)
                    throw;

                Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in Deserialize<{typeof(T)}>(): {e.Message}");
                return default;
            }
        }

        public static object? Deserialize(Type type, Stream? stream)
        {
            if (stream == null)
                return null;

            object? result = null;
            var settings = SerializerSettings();
            using (var sr = new StreamReader(stream))
            {
                using (JsonReader reader = new JsonTextReader(sr))
                {
                    var serializer = JsonSerializer.Create(settings);
                    result = serializer.Deserialize(reader, type);
                }
            }

            return result;
        }

        [return: MaybeNull]
        public static T Deserialize<T>(string? json, bool strict = false) // where T : new()
        {
            var ret = default(T);
            if (string.IsNullOrWhiteSpace(json))
                return ret;

            try
            {
                var settings = SerializerSettings();
                //if (typeof(T).IsSubclassOf(typeof(BaseObject)))
                //{
                //    ret = Activator.CreateInstance<T>();
                //    (ret as BaseObject).SetObserving(false);
                //    JsonConvert.PopulateObject(json, ret, settings);
                //    (ret as BaseObject).SetObserving(true);
                //}
                //else 
                if (typeof(T).IsArray)
                {
                    object o = Array.CreateInstance(typeof(T).GetElementType(), 0);
                    ret = (T)o;
                }
                else
                {
                    ret = JsonConvert.DeserializeObject<T>(json, settings);
                }
            }
            catch (Exception)
            {
                if (strict)
                {
                    throw;
                }
                ret = Activator.CreateInstance<T>();
            }

            return ret;
        }

        public static object? Deserialize(Type T, string json) // where T : new()
        {
            var ret = T.GetDefault();
            if (string.IsNullOrWhiteSpace(json))
                return ret;

            try
            {
                var settings = SerializerSettings();
                //if (typeof(T).IsSubclassOf(typeof(BaseObject)))
                //{
                //    ret = Activator.CreateInstance<T>();
                //    (ret as BaseObject).SetObserving(false);
                //    JsonConvert.PopulateObject(json, ret, settings);
                //    (ret as BaseObject).SetObserving(true);
                //}
                //else 
                if (T.IsArray)
                {
                    object o = Array.CreateInstance(T.GetElementType(), 0);
                    ret = o;
                }
                else
                {
                    ret = JsonConvert.DeserializeObject(json, T, settings);
                }
            }
            catch (Exception)
            {
                ret = Activator.CreateInstance(T);
            }

            return ret;
        }

        #region Binary Serialization

        public static byte[] WriteBinary(this ISerializeBinary obj)
        {
            using var stream = new MemoryStream();
            obj.SerializeBinary(new BinaryWriter(stream));
            return stream.ToArray();
        }

        public static T ReadBinary<T>(byte[] data)
            where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), data);
        public static T ReadBinary<T>(Stream stream)
            where T : ISerializeBinary, new() => (T)ReadBinary(typeof(T), stream);

        public static object ReadBinary(Type T, byte[] data)
        {
            using var stream = new MemoryStream(data);
            return ReadBinary(T, stream);
        }
        public static object ReadBinary(Type T, Stream stream)
        {
            var obj = (Activator.CreateInstance(T) as ISerializeBinary)!;
            obj.DeserializeBinary(new BinaryReader(stream));
            return obj;
        }

        #endregion
    }
}