//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// Copyright (c) 2008 - 2015 Jb Evain
// Copyright (c) 2008 - 2011 Novell, Inc.
//
// Licensed under the MIT/X11 license.
//

using System;

namespace MonoFN.Cecil.PE
{
    internal class ByteBuffer
    {
        internal byte[] buffer;
        internal int length;
        internal int position;

        public ByteBuffer()
        {
            buffer = Empty<byte>.Array;
        }

        public ByteBuffer(int length)
        {
            buffer = new byte [length];
        }

        public ByteBuffer(byte[] buffer)
        {
            this.buffer = buffer ?? Empty<byte>.Array;
            length = this.buffer.Length;
        }

        public void Advance(int length)
        {
            position += length;
        }

        public byte ReadByte()
        {
            return buffer[position++];
        }

        public sbyte ReadSByte()
        {
            return (sbyte)ReadByte();
        }

        public byte[] ReadBytes(int length)
        {
            byte[] bytes = new byte [length];
            Buffer.BlockCopy(buffer, position, bytes, 0, length);
            position += length;
            return bytes;
        }

        public ushort ReadUInt16()
        {
            ushort value = (ushort)(buffer[position] | (buffer[position + 1] << 8));
            position += 2;
            return value;
        }

        public short ReadInt16()
        {
            return (short)ReadUInt16();
        }

        public uint ReadUInt32()
        {
            uint value = (uint)(buffer[position] | (buffer[position + 1] << 8) | (buffer[position + 2] << 16) | (buffer[position + 3] << 24));
            position += 4;
            return value;
        }

        public int ReadInt32()
        {
            return (int)ReadUInt32();
        }

        public ulong ReadUInt64()
        {
            uint low = ReadUInt32();
            uint high = ReadUInt32();

            return ((ulong)high << 32) | low;
        }

        public long ReadInt64()
        {
            return (long)ReadUInt64();
        }

        public uint ReadCompressedUInt32()
        {
            byte first = ReadByte();
            if ((first & 0x80) == 0)
                return first;

            if ((first & 0x40) == 0)
                return ((uint)(first & ~0x80) << 8) | ReadByte();

            return ((uint)(first & ~0xc0) << 24) | ((uint)ReadByte() << 16) | ((uint)ReadByte() << 8) | ReadByte();
        }

        public int ReadCompressedInt32()
        {
            byte b = buffer[position];
            int u = (int)ReadCompressedUInt32();
            int v = u >> 1;
            if ((u & 1) == 0)
                return v;

            switch (b & 0xc0)
            {
                case 0:
                case 0x40:
                    return v - 0x40;
                case 0x80:
                    return v - 0x2000;
                default:
                    return v - 0x10000000;
            }
        }

        public float ReadSingle()
        {
            if (!BitConverter.IsLittleEndian)
            {
                byte[] bytes = ReadBytes(4);
                Array.Reverse(bytes);
                return BitConverter.ToSingle(bytes, 0);
            }

            float value = BitConverter.ToSingle(buffer, position);
            position += 4;
            return value;
        }

        public double ReadDouble()
        {
            if (!BitConverter.IsLittleEndian)
            {
                byte[] bytes = ReadBytes(8);
                Array.Reverse(bytes);
                return BitConverter.ToDouble(bytes, 0);
            }

            double value = BitConverter.ToDouble(buffer, position);
            position += 8;
            return value;
        }

        public void WriteByte(byte value)
        {
            if (position == buffer.Length)
                Grow(1);

            buffer[position++] = value;

            if (position > length)
                length = position;
        }

        public void WriteSByte(sbyte value)
        {
            WriteByte((byte)value);
        }

        public void WriteUInt16(ushort value)
        {
            if (position + 2 > buffer.Length)
                Grow(2);

            buffer[position++] = (byte)value;
            buffer[position++] = (byte)(value >> 8);

            if (position > length)
                length = position;
        }

        public void WriteInt16(short value)
        {
            WriteUInt16((ushort)value);
        }

        public void WriteUInt32(uint value)
        {
            if (position + 4 > buffer.Length)
                Grow(4);

            buffer[position++] = (byte)value;
            buffer[position++] = (byte)(value >> 8);
            buffer[position++] = (byte)(value >> 16);
            buffer[position++] = (byte)(value >> 24);

            if (position > length)
                length = position;
        }

        public void WriteInt32(int value)
        {
            WriteUInt32((uint)value);
        }

        public void WriteUInt64(ulong value)
        {
            if (position + 8 > buffer.Length)
                Grow(8);

            buffer[position++] = (byte)value;
            buffer[position++] = (byte)(value >> 8);
            buffer[position++] = (byte)(value >> 16);
            buffer[position++] = (byte)(value >> 24);
            buffer[position++] = (byte)(value >> 32);
            buffer[position++] = (byte)(value >> 40);
            buffer[position++] = (byte)(value >> 48);
            buffer[position++] = (byte)(value >> 56);

            if (position > length)
                length = position;
        }

        public void WriteInt64(long value)
        {
            WriteUInt64((ulong)value);
        }

        public void WriteCompressedUInt32(uint value)
        {
            if (value < 0x80)
            {
                WriteByte((byte)value);
            }
            else if (value < 0x4000)
            {
                WriteByte((byte)(0x80 | (value >> 8)));
                WriteByte((byte)(value & 0xff));
            }
            else
            {
                WriteByte((byte)((value >> 24) | 0xc0));
                WriteByte((byte)((value >> 16) & 0xff));
                WriteByte((byte)((value >> 8) & 0xff));
                WriteByte((byte)(value & 0xff));
            }
        }

        public void WriteCompressedInt32(int value)
        {
            if (value >= 0)
            {
                WriteCompressedUInt32((uint)(value << 1));
                return;
            }

            if (value > -0x40)
                value = 0x40 + value;
            else if (value >= -0x2000)
                value = 0x2000 + value;
            else if (value >= -0x20000000)
                value = 0x20000000 + value;

            WriteCompressedUInt32((uint)((value << 1) | 1));
        }

        public void WriteBytes(byte[] bytes)
        {
            int length = bytes.Length;
            if (position + length > buffer.Length)
                Grow(length);

            Buffer.BlockCopy(bytes, 0, buffer, position, length);
            position += length;

            if (position > this.length)
                this.length = position;
        }

        public void WriteBytes(int length)
        {
            if (position + length > buffer.Length)
                Grow(length);

            position += length;

            if (position > this.length)
                this.length = position;
        }

        public void WriteBytes(ByteBuffer buffer)
        {
            if (position + buffer.length > this.buffer.Length)
                Grow(buffer.length);

            Buffer.BlockCopy(buffer.buffer, 0, this.buffer, position, buffer.length);
            position += buffer.length;

            if (position > length)
                length = position;
        }

        public void WriteSingle(float value)
        {
            byte[] bytes = BitConverter.GetBytes(value);

            if (!BitConverter.IsLittleEndian)
                Array.Reverse(bytes);

            WriteBytes(bytes);
        }

        public void WriteDouble(double value)
        {
            byte[] bytes = BitConverter.GetBytes(value);

            if (!BitConverter.IsLittleEndian)
                Array.Reverse(bytes);

            WriteBytes(bytes);
        }

        private void Grow(int desired)
        {
            byte[] current = this.buffer;
            int current_length = current.Length;

            byte[] buffer = new byte [Math.Max(current_length + desired, current_length * 2)];
            Buffer.BlockCopy(current, 0, buffer, 0, current_length);
            this.buffer = buffer;
        }
    }
}