using System;
using System.Text;

namespace InABox.Core
{
    public class FastBinaryReader
    {
        private readonly byte[] _data;
        private int index = -1;

        public FastBinaryReader(byte[] data)
        {
            _data = data;
            index = 0;
        }

        public bool ReadBoolean()
        {
            var result = _data[index] != 0;
            index++;
            return result;
        }

        public byte ReadByte()
        {
            var result = _data[index];
            index++;
            return result;
        }

        public short ReadInt16()
        {
            var result = BitConverter.ToInt16(_data, index);
            index += 2;
            return result;
        }

        public int ReadInt32()
        {
            var result = BitConverter.ToInt32(_data, index);
            index += 4;
            return result;
        }

        public long ReadInt64()
        {
            var result = BitConverter.ToInt64(_data, index);
            index += 8;
            return result;
        }

        public double ReadFloat()
        {
            var result = BitConverter.ToSingle(_data, index);
            index += 4;
            return result;
        }

        public double ReadDouble()
        {
            var result = BitConverter.ToDouble(_data, index);
            index += 8;
            return result;
        }


        protected internal int Read7BitEncodedInt()
        {
            // Read out an Int32 7 bits at a time.  The high bit
            // of the byte when on means to continue reading more bytes.
            var count = 0;
            var shift = 0;
            byte b;
            do
            {
                // Check for a corrupted stream.  Read a max of 5 bytes.
                // In a future version, add a DataFormatException.
                if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
                    throw new FormatException("Format_Bad7BitInt32");

                // ReadByte handles end of stream cases for us.
                b = ReadByte();
                count |= (b & 0x7F) << shift;
                shift += 7;
            } while ((b & 0x80) != 0);

            return count;
        }


        public string ReadString()
        {
            var length = Read7BitEncodedInt();
            var result = Encoding.UTF8.GetString(_data, index, length);
            index += length;
            return result;
        }

        public DateTime ReadDateTime()
        {
            var result = ReadInt64();
            return DateTime.FromBinary(result);
        }

        public TimeSpan ReadTimeSpan()
        {
            var result = ReadInt64();
            return new TimeSpan(result);
        }

        public Guid ReadGuid()
        {
            var buf = new byte[16];
            Buffer.BlockCopy(_data, index, buf, 0, 16);
            var result = new Guid(buf);
            index += 16;
            return result;
        }

        public byte[] ReadBytes(int length)
        {
            var result = new byte[length];
            Buffer.BlockCopy(_data, index, result, 0, length);
            index += length;
            return result;
        }
    }
}