using System;
using System.Collections.Generic;

namespace InABox.Core
{
    public abstract class PackableDictionary<T> : Dictionary<string, T>, IPackable
    {
        public void Pack(FastBinaryWriter writer)
        {
            writer.Write(Count);
            foreach (var key in Keys)
            {
                writer.Write(key);
                PackItem(writer, this[key]);
            }
        }

        public void Unpack(FastBinaryReader reader)
        {
            Clear();
            var iCount = reader.ReadInt32();
            for (var i = 0; i < iCount; i++)
            {
                var key = reader.ReadString();
                this[key] = UnpackItem(reader);
            }
        }

        public override bool Equals(object obj)
        {
            var other = obj as PackableDictionary<T>;
            if (other == null)
                return false;

            if (Count != other.Count) // Require equal count.
                return false;

            foreach (var pair in this)
                if (other.TryGetValue(pair.Key, out var value))
                {
                    if (value == null)
                    {
                        if (pair.Value != null)
                            return false;
                    }
                    else if (!value.Equals(pair.Value))
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }

            return true;
        }

        public abstract void PackItem(FastBinaryWriter writer, T value);

        public abstract T UnpackItem(FastBinaryReader reader);
    }

    public class PackableStringDictionary : PackableDictionary<string>
    {
        public override void PackItem(FastBinaryWriter writer, string value)
        {
            writer.Write(value);
        }

        public override string UnpackItem(FastBinaryReader reader)
        {
            return reader.ReadString();
        }
    }

    public class PackableBooleanDictionary : PackableDictionary<bool>
    {
        public override void PackItem(FastBinaryWriter writer, bool value)
        {
            writer.Write(value);
        }

        public override bool UnpackItem(FastBinaryReader reader)
        {
            return reader.ReadBoolean();
        }
    }

    public class PackableObjectDictionary : PackableDictionary<object>
    {
        private static readonly Dictionary<Type, short> Types = new Dictionary<Type, short>
        {
            { typeof(object), 0x0000 },
            { typeof(string), 0x0001 },
            { typeof(string[]), 0x002 },
            { typeof(sbyte), 0x1000 },
            { typeof(byte), 0x0011 },
            { typeof(ushort), 0x0012 },
            { typeof(uint), 0x0013 },
            { typeof(ulong), 0x0014 },
            { typeof(short), 0x0015 },
            { typeof(int), 0x0016 },
            { typeof(long), 0x0017 },
            { typeof(float), 0x0100 },
            { typeof(double), 0x0101 },
            { typeof(decimal), 0x0102 },
            { typeof(TimeSpan), 0x1000 },
            { typeof(DateTime), 0x1001 },
            { typeof(Guid), 0x1002 }
        };

        private static readonly Dictionary<short, Type> Indexes = new Dictionary<short, Type>();

        static PackableObjectDictionary()
        {
            foreach (var key in Types.Keys)
                Indexes[Types[key]] = key;
        }

        private static short GetIndex(Type type)
        {
            if (Types.ContainsKey(type))
                return Types[type];
            return 0x0000;
        }

        public static Type GetType(short index)
        {
            if (Indexes.ContainsKey(index))
                return Indexes[index];
            return typeof(object);
        }


        public override void PackItem(FastBinaryWriter writer, object value)
        {
            if (value == null)
            {
                writer.Write(default(short));
                writer.Write("");
            }
            else
            {
                var type = GetIndex(value.GetType());
                writer.Write(type);
                writer.Write(CoreUtils.TypedValueToString(value));
            }
        }

        public override object UnpackItem(FastBinaryReader reader)
        {
            var index = reader.ReadInt16();
            var type = GetType(index);
            var value = reader.ReadString();
            if (type == typeof(object) && string.IsNullOrEmpty(value))
                return null;
            return CoreUtils.StringToTypedValue(value, type);
        }
    }
}