using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper5.Skyscraper.IO
{
    class InBitStream
    {
        public InBitStream(byte[] buffer)
        : this(new MemoryStream(buffer,false))
        {
            
        }
        public InBitStream(MemoryStream wrapped)
        {
            _wrapped = wrapped;
            bitPointer = 8;
            bits = new bool[8];
        }

        private readonly MemoryStream _wrapped;
        private byte bitPointer;
        private bool[] bits;

        private byte ReadBit()
        {
            if (bitPointer == 8)
            {
                byte readUInt8 = _wrapped.ReadUInt8();
                bits[0] = (readUInt8 & 0x80) != 0;
                bits[1] = (readUInt8 & 0x40) != 0;
                bits[2] = (readUInt8 & 0x20) != 0;
                bits[3] = (readUInt8 & 0x10) != 0;
                bits[4] = (readUInt8 & 0x08) != 0;
                bits[5] = (readUInt8 & 0x04) != 0;
                bits[6] = (readUInt8 & 0x02) != 0;
                bits[7] = (readUInt8 & 0x01) != 0;
                bitPointer = 0;
            }

            bool result = bits[bitPointer++];
            return (byte)(result ? 0x01 : 0x00);
        }

        public uint ReadBitsAsUint(byte amount)
        {
            if (amount > 32)
                throw new ArgumentOutOfRangeException(nameof(amount));

            uint result = 0;
            for (byte i = 0; i < amount; i++)
            {
                result <<= 1;
                result += ReadBit();
            }

            return result;
        }

        public ulong ReadBitsAsULong(byte amount)
        {
            if (amount > 64)
                throw new ArgumentOutOfRangeException(nameof(amount));

            ulong result = 0;
            for (byte i = 0; i < amount; i++)
            {
                result <<= 1;
                result += ReadBit();
            }
            return result;
        }

        public byte ReadBitsAsByte(byte amount)
        {
            if (amount > 8)
                throw new ArgumentOutOfRangeException(nameof(amount));

            return (byte)ReadBitsAsUint(amount);
        }

        public bool ReadBitAsBoolean()
        {
            return ReadBit() == 1 ? true : false;
        }

        public ushort ReadBitsAsUInt16(byte amount)
        {
            if (amount > 16)
                throw new ArgumentOutOfRangeException(nameof(amount));

			ushort result = 0;
			for (byte i = 0; i < amount; i++)
			{
				result <<= 1;
				result += ReadBit();
			}
			return result;
		}

        public void SkipBits(ulong amount)
        {
            for (ulong i = 0; i < amount; i++)
                ReadBit();
        }

		public byte[] ReadBitsAsByteArray(uint v)
		{
            byte[] result = new byte[v / 8];
            int offset = 0;
            while (v > 0)
            {
                uint nextSize = Math.Min(8, v);
                result[offset++] = ReadBitsAsByte(8);
                v -= nextSize;
            }
            return result;
		}

		public bool IsByteAligned => bitPointer == 8;

        public long GetBitsAvailable()
        {
            long result = _wrapped.GetAvailableBytes();
            result += (8 - bitPointer);
            return result;
        }

        public void Reserved(byte i)
        {
            ReadBitsAsULong(i);
        }
    }
}
