using System;
using System.Collections;
using System.Text;

namespace XGame.Framework.Serialization
{
    public class Writer : IWriter
    {
        private const int DefaultLength = 2048;
        private byte[] _buffer;
        private int _size;

        public Writer()
        {
            _buffer = new byte[DefaultLength];
            _size = 0;
        }

        public void Write(bool value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(sbyte value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(byte value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(short value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(ushort value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(int value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(uint value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(long value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(ulong value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(float value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(double value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(char value)
        {
            var bytes = BitConverter.GetBytes(value);
            var length = bytes.Length;
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(string value)
        {
            if (value == null)
            {
                Write(-1);
                return;
            }

            if (value == string.Empty)
            {
                Write(0);
                return;
            }

            var bytes = Encoding.UTF8.GetBytes(value);
            var length = bytes.Length;
            Write(length);
            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(bytes, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(byte[] value)
        {
            if (value == null)
            {
                Write(-1);
                return;
            }
;
            var length = value.Length;
            Write(length);

            if (_size + length >= _buffer.Length)
                AllocateMore();

            Buffer.BlockCopy(value, 0, _buffer, _size, length);
            _size += length;
        }

        public void Write(ISerializable data)
        {
            bool isNull = data == null;
            Write(isNull);
            if (isNull) return;

            data.Serialize(this);
        }

        public void Write(IEnumerable enumerable)
        {
            if (null == enumerable)
            {
                Write(-1);
                return;
            }

            Assert.IsTrue(!(enumerable is Array array) || array.Rank == 1, "");
            switch (enumerable)
            {
                case IDictionary dic:
                    WriteDictionary(dic);
                    break;
                case IList ls:  // Array & List
                    WriteList(ls);
                    break;
                default:
                    throw new NotSupportedException($"不支持序列化 {enumerable.GetType()} 类型的字段");
            }
        }

        public byte[] Finish()
        {
            var resBuffer = new byte[_size];
            Buffer.BlockCopy(_buffer, 0, resBuffer, 0, _size);
            _buffer = null;
            _size = 0;
            return resBuffer;
        }

        private void WriteList(IList list)
        {
            Write(list.Count);
            var e = list.GetEnumerator();
            while (e.MoveNext())
            {
                WriteObject(e.Current);
            }
        }

        private void WriteDictionary(IDictionary dic)
        {
            Write(dic.Count);
            var e = dic.GetEnumerator();
            while (e.MoveNext())
            {
                WriteObject(e.Key);
                WriteObject(e.Value);
            }
        }

        private void WriteObject(object data)
        {
            if (null == data)
            {
                var isNull = true;
                Write(isNull);
                return;
            }

            if (data.GetType().IsEnum)
            {
                var type = data.GetType().GetEnumUnderlyingType();
                data = Convert.ChangeType(data, type);
            }

            switch (data)
            {
                case bool value: Write(value); break;
                case byte value: Write(value); break;
                case sbyte value: Write(value); break;
                case short value: Write(value); break;
                case ushort value: Write(value); break;
                case int value: Write(value); break;
                case uint value: Write(value); break;
                case long value: Write(value); break;
                case ulong value: Write(value); break;
                case float value: Write(value); break;
                case double value: Write(value); break;
                case char value: Write(value); break;
                case string value: Write(value); break;
                case byte[] value: Write(value); break;
                case IEnumerable value: Write(value); break;
                case ISerializable value: Write(value); break;
                default: throw new NotSupportedException($"Reader::ReadEnumerable: {data.GetType()} is unsupported.");
            }

            return;
        }

        private void AllocateMore(int length = 0)
        {
            var size = _buffer.Length << (length / _buffer.Length / 2 + 1);
            var array = new byte[size];
            if (_buffer != null && _size > 0)
            {
                _buffer.CopyTo(array, 0);
            }

            _buffer = array;
        }
    }
}
