using NPOI.SS.Formula.Functions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;

namespace InABox.DigitalMatter
{
    public interface IDMBuffer
    {
        /// <summary>
        /// The remaining size of the buffer.
        /// </summary>
        public ushort BufferSize { get; }


        #region Bit Manipulation

        private static bool[] DecodeBits(byte[] value, int index, int length)
        {
            var result = new List<bool>();
            var array = new BitArray(value);
            for (var i = index; i < index + length && i < array.Count; i++)
                result.Add(array[i]);
            return result.ToArray();
        }

        private static byte[] EncodeBits(byte[] source, int index, bool[] bits)
        {
            var array = new BitArray(source);
            for (var i = 0; i < bits.Length; i++)
                array.Set(index + i, bits[i]);
            var result = new byte[source.Length];
            array.CopyTo(result, 0);
            return result;
        }

        public static bool[] GetBits(byte[] value, int index, int length)
        {
            return DecodeBits(value, index, length);
        }

        public static byte[] SetBits(byte[] value, int index, bool[] bits)
        {
            return EncodeBits(value, index, bits);
        }

        public static bool[] DecodeByte(byte value, int index, int length)
        {
            return DecodeBits(new[] { value }, index, length);
        }

        public static bool[] DecodeBytes(byte[] value, int index, int length)
        {
            return DecodeBits(value, index, length);
        }

        public static bool[] DecodeInt8(sbyte value, int index, int length)
        {
            return DecodeBits(new[] { Convert.ToByte(value) }, index, length);
        }

        public static bool[] DecodeInt16(short value, int index, int length)
        {
            return DecodeBits(BitConverter.GetBytes(value), index, length);
        }

        public static bool[] DecodeUInt16(ushort value, int index, int length)
        {
            return DecodeBits(BitConverter.GetBytes(value), index, length);
        }

        public static bool[] DecodeInt32(int value, int index, int length)
        {
            return DecodeBits(BitConverter.GetBytes(value), index, length);
        }

        public static bool[] DecodeUInt32(uint value, int index, int length)
        {
            return DecodeBits(BitConverter.GetBytes(value), index, length);
        }

        public static byte EncodeByte(bool[] bits)
        {
            return EncodeBits(new byte[] { default }, 0, bits).First();
        }

        public static byte[] EncodeBytes(bool[] bits, int size)
        {
            return EncodeBits(Array.CreateInstance(typeof(byte), size).OfType<byte>().ToArray(), 0, bits);
        }

        public static sbyte EncodeInt8(bool[] bits)
        {
            return Convert.ToSByte(EncodeBits(new[] { Convert.ToByte(default(sbyte)) }, 0, bits).First());
        }

        public static short EncodeInt16(bool[] bits)
        {
            return BitConverter.ToInt16(EncodeBits(BitConverter.GetBytes(default(short)), 0, bits), 0);
        }

        public static ushort EncodeUInt16(bool[] bits)
        {
            return BitConverter.ToUInt16(EncodeBits(BitConverter.GetBytes(default(ushort)), 0, bits), 0);
        }

        public static int EncodeInt32(bool[] bits)
        {
            return BitConverter.ToInt32(EncodeBits(BitConverter.GetBytes(default(int)), 0, bits), 0);
        }

        public static uint EncodeUInt32(bool[] bits)
        {
            return BitConverter.ToUInt32(EncodeBits(BitConverter.GetBytes(default(uint)), 0, bits), 0);
        }

        public static byte UpdateByte(byte value, int index, bool[] bits)
        {
            return EncodeBits(new[] { value }, index, bits).First();
        }

        public static byte[] UpdateBytes(byte[] value, int index, bool[] bits, int size)
        {
            return EncodeBits(value, 0, bits);
        }

        public static sbyte UpdateInt8(sbyte value, int index, bool[] bits)
        {
            return Convert.ToSByte(EncodeBits(new[] { Convert.ToByte(value) }, 0, bits).First());
        }

        public static short UpdateInt16(short value, int index, bool[] bits)
        {
            return BitConverter.ToInt16(EncodeBits(BitConverter.GetBytes(value), 0, bits), 0);
        }

        public static ushort UpdateUInt16(ushort value, int index, bool[] bits)
        {
            return BitConverter.ToUInt16(EncodeBits(BitConverter.GetBytes(value), 0, bits), 0);
        }

        public static int UpdateInt32(int value, int index, bool[] bits)
        {
            return BitConverter.ToInt32(EncodeBits(BitConverter.GetBytes(value), 0, bits), 0);
        }

        public static uint UpdateUInt32(uint value, int index, bool[] bits)
        {
            return BitConverter.ToUInt32(EncodeBits(BitConverter.GetBytes(value), 0, bits), 0);
        }

        #endregion
    }

    public interface IDMReadBuffer : IDMBuffer
    {
        /// <summary>
        /// Read <paramref name="length"/> bytes, starting at <paramref name="index"/>, but don't advance the read pointer.
        /// </summary>
        /// <param name="index">Index to start the read.</param>
        /// <param name="length">Number of bytes to read.</param>
        /// <returns>The bytes read.</returns>
        protected byte[] Peek(int index, int length);

        /// <summary>
        /// Read <paramref name="length"/> bytes, advancing the read pointer by <paramref name="length"/>.
        /// </summary>
        /// <param name="length">Number of bytes to read.</param>
        /// <returns>The bytes read.</returns>
        protected byte[] Take(int length);

        public byte PeekByte(int index)
        {
            return Peek(index, sizeof(byte)).First();
        }

        public byte[] PeekBytes(int index, int length)
        {
            return Peek(index, length);
        }

        public sbyte PeekInt8(int index)
        {
            return unchecked((sbyte)PeekByte(index));
            //hmmm...
        }

        public ushort PeekUInt16(int index)
        {
            return BitConverter.ToUInt16(Peek(index, sizeof(ushort)), 0);
        }

        public short PeekInt16(int index)
        {
            return BitConverter.ToInt16(Peek(index, sizeof(short)), 0);
        }

        public uint PeekUInt32(int index)
        {
            return BitConverter.ToUInt32(Peek(index, sizeof(uint)), 0);
        }

        public int PeekInt32(int index)
        {
            return BitConverter.ToInt32(Peek(index, sizeof(int)), 0);
        }

        public string PeekString(int index, int length)
        {
            return Encoding.Default.GetString(Peek(index, length)).Trim();
        }

        public byte TakeByte()
        {
            return Take(sizeof(byte)).First();
        }

        public byte[] TakeBytes(int length)
        {
            return Take(length);
        }

        public sbyte TakeInt8()
        {
            return unchecked((sbyte)TakeByte());
            //hmmm...
        }

        public ushort TakeUInt16()
        {
            return BitConverter.ToUInt16(Take(sizeof(ushort)), 0);
        }

        public short TakeInt16()
        {
            return BitConverter.ToInt16(Take(sizeof(short)), 0);
        }

        public uint TakeUInt32()
        {
            return BitConverter.ToUInt32(Take(sizeof(uint)), 0);
        }

        public int TakeInt32()
        {
            return BitConverter.ToInt32(Take(sizeof(int)), 0);
        }

        public string TakeString(int length)
        {
            return Encoding.Default.GetString(Take(length)).Trim();
        }
    }

    public interface IDMWriteBuffer : IDMBuffer
    {
        protected ushort Add(byte[] bytes);

        protected ushort Insert(int index, byte[] bytes);

        void Reset();

        public ushort AddByte(byte value)
        {
            return Add(new[] { value });
        }

        public ushort AddBytes(byte[] value)
        {
            return Add(value);
        }

        public ushort AddInt8(sbyte value)
        {
            return Add(BitConverter.GetBytes((short)value));
        }

        public ushort AddUInt16(ushort value)
        {
            return Add(BitConverter.GetBytes(value));
        }

        public ushort AddInt16(short value)
        {
            return Add(BitConverter.GetBytes(value));
        }

        public ushort AddUInt32(uint value)
        {
            return Add(BitConverter.GetBytes(value));
        }

        public ushort AddInt32(int value)
        {
            return Add(BitConverter.GetBytes(value));
        }

        public ushort AddString(string value, int length)
        {
            return Add(FixedLengthString(value, length));
        }

        public ushort InsertByte(int index, byte value)
        {
            return Insert(index, new[] { value });
        }

        public ushort InsertBytes(int index, byte[] value)
        {
            return Insert(index, value);
        }

        public ushort InsertInt8(int index, sbyte value)
        {
            return Insert(index, BitConverter.GetBytes((short)value));
        }

        public ushort InsertUInt16(int index, ushort value)
        {
            return Insert(index, BitConverter.GetBytes(value));
        }

        public ushort InsertInt16(int index, short value)
        {
            return Insert(index, BitConverter.GetBytes(value));
        }

        public ushort InsertUInt32(int index, uint value)
        {
            return Insert(index, BitConverter.GetBytes(value));
        }

        public ushort InsertInt32(int index, int value)
        {
            return Insert(index, BitConverter.GetBytes(value));
        }

        public ushort InsertString(int index, string value, int length)
        {
            return Insert(index, FixedLengthString(value, length));
        }

        #region Private Members

        private static byte[] FixedLengthString(string value, int length)
        {
            var result = Encoding.Default.GetBytes(value).ToList();
            while (result.Count < length)
                result.Add(0x00);
            return result.ToArray();
        }

        #endregion
    }

    public class DMArrayReadBuffer : IDMReadBuffer
    {
        private byte[] _buffer { get; set; }
        private int _offset;

        public ushort BufferSize => (ushort)(_buffer.Length - _offset);

        public DMArrayReadBuffer(byte[] buffer)
        {
            _buffer = buffer;
            _offset = 0;
        }

        byte[] IDMReadBuffer.Peek(int index, int length)
        {
            index += _offset;
            length = Math.Min(length, _buffer.Length - index);
            var newBytes = new byte[length];
            Buffer.BlockCopy(_buffer, index, newBytes, 0, length);
            return newBytes;
        }

        byte[] IDMReadBuffer.Take(int length)
        {
            length = Math.Min(length, _buffer.Length - _offset);
            var newBytes = new byte[length];
            Buffer.BlockCopy(_buffer, _offset, newBytes, 0, length);
            _offset += length;
            return newBytes;
        }
    }
    public class DMListReadBuffer : IDMReadBuffer
    {
        private List<byte> _buffer { get; set; }
        private int _offset;

        public ushort BufferSize => (ushort)(_buffer.Count - _offset);

        public DMListReadBuffer(List<byte> buffer)
        {
            _buffer = buffer;
            _offset = 0;
        }

        byte[] IDMReadBuffer.Peek(int index, int length) => Peek(index, length);

        private byte[] Peek(int index, int length)
        {
            index += _offset;
            length = Math.Min(length, _buffer.Count - index);
            var newBytes = new byte[length];
            _buffer.CopyTo(index, newBytes, 0, length);
            return newBytes;
        }
        
        byte[] IDMReadBuffer.Take(int length)
        {
            var result = Peek(0, length);
            _offset += length;
            return result;
        }
    }

    public class DMPartialReadBuffer : IDMReadBuffer
    {
        private ushort _offset;
        private ushort _length;

        public IDMReadBuffer Buffer { get; }
        public ushort BufferSize => (ushort)(_length - (Offset - _offset));
        public ushort Offset { get; private set; }

        public DMPartialReadBuffer(IDMReadBuffer buffer, ushort offset, ushort length)
        {
            _length = length;
            _offset = offset;
            Offset = offset;
            Buffer = buffer;
        }

        byte[] IDMReadBuffer.Peek(int index, int length) => Peek(index, length);

        private byte[] Peek(int index, int length)
        {
            index += Offset;
            length = Math.Min(length, _offset + _length - index);
            return Buffer.PeekBytes(index, length);
        }

        byte[] IDMReadBuffer.Take(int length)
        {
            var bytes = Peek(0, length);
            Offset += (ushort)bytes.Length;
            return bytes;
        }
    }

    public class DMListWriteBuffer : IDMWriteBuffer
    {
        private List<byte> _buffer { get; set; }
        private int _offset;

        public List<byte> Buffer => _buffer;

        public ushort BufferSize => (ushort)(_buffer.Count - _offset);

        public DMListWriteBuffer(List<byte> buffer)
        {
            _buffer = buffer;
            _offset = 0;
        }

        public void Reset()
        {
            _buffer.Clear();
        }

        ushort IDMWriteBuffer.Add(byte[] bytes)
        {
            _buffer.AddRange(bytes);
            return BufferSize;
        }

        ushort IDMWriteBuffer.Insert(int index, byte[] bytes)
        {
            _buffer.InsertRange(index, bytes);
            return BufferSize;
        }
    }

    public class DMBuffer : IDMReadBuffer, IDMWriteBuffer
    {
        public byte[] Buffer
        {
            get => _buffer.ToArray();
            set => _buffer = value.ToList();
        }

        public DMBuffer() { }

        public DMBuffer(byte[] buffer)
        {
            Buffer = buffer;
        }

        public DMBuffer(List<byte> buffer)
        {
            _buffer = buffer;
        }

        public ushort BufferSize => (ushort)_buffer.Count;

        public void Reset()
        {
            _buffer = new List<byte>();
        }


        #region Private Members

        private List<byte> _buffer = new();

        ushort IDMWriteBuffer.Add(byte[] bytes)
        {
            _buffer.AddRange(bytes);
            return BufferSize;
        }

        ushort IDMWriteBuffer.Insert(int index, byte[] bytes)
        {
            _buffer.InsertRange(index, bytes);
            return BufferSize;
        }
        private byte[] Peek(int index, int length)
        {
            return _buffer.Skip(index).Take(length).ToArray();
        }

        byte[] IDMReadBuffer.Peek(int index, int length) => Peek(index, length);

        byte[] IDMReadBuffer.Take(int length)
        {
            var result = Peek(0, length);
            _buffer.RemoveRange(0, length);
            return result;
        }

        #endregion

    }
}