using System;
using System.IO;
using System.Text;

namespace FiestaBot.Instances.Networking
{
    internal class Packet : IDisposable
    {
        private MemoryStream PacketStream;
        private BinaryWriter PacketWriter;
        private BinaryReader PacketReader;

        public int Remaining
        {
            get { return Convert.ToInt32(PacketStream.Length - PacketStream.Position); }
        }

        public ushort OPCode;
        public byte Header;
        public byte Type;

        public Packet(byte[] Buffer)
        {
            PacketStream = new MemoryStream(Buffer);
            PacketReader = new BinaryReader(PacketStream);
        }

        public bool SetHeaderAndType()
        {
            ReadUShort(out OPCode);

            try
            {
                Header = Convert.ToByte(OPCode >> 10);
                Type = Convert.ToByte(OPCode & 1023);

                return true;
            }
            catch { return false; }
        }

        public Packet(byte TransferHeader, byte TransferType)
        {
            PacketStream = new MemoryStream();
            PacketWriter = new BinaryWriter(PacketStream);

            Header = TransferHeader;
            Type = TransferType;

            OPCode = Convert.ToUInt16((Header << 10) + (Type & 1023));

            WriteUShort(OPCode);
        }

        public bool ReadUShort(out ushort Value)
        {
            if (Remaining < 2)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadUInt16();

            return true;
        }

        public bool ReadShort(out short Value)
        {
            if (Remaining < 2)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadInt16();

            return true;
        }

        public bool ReadInt(out int Value)
        {
            if (Remaining < 4)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadInt32();

            return true;
        }

        public bool ReadUInt(out uint Value)
        {
            if (Remaining < 4)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadUInt32();

            return true;
        }

        public bool ReadLong(out long Value)
        {
            if (Remaining < 8)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadInt64();

            return true;
        }

        public bool ReadULong(out ulong Value)
        {
            if (Remaining < 8)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadUInt64();

            return true;
        }

        public bool ReadString(out string Value)
        {
            byte Length;

            if (!ReadByte(out Length))
            {
                Value = string.Empty;

                return false;
            }

            if (Remaining < Length)
            {
                Value = string.Empty;

                return false;
            }

            return ReadString(out Value, Length);
        }

        public bool ReadString(out string Value, int ReadLength)
        {
            if (Remaining < ReadLength)
            {
                Value = string.Empty;

                return false;
            }

            byte[] Buffer = new byte[ReadLength];

            if(!ReadBytes(Buffer))
            {
                Value = string.Empty;

                return false;
            }

            int Length = 0;

            if (Buffer[ReadLength - 1] != 0) { Length = ReadLength; }
            else
            {
                while (Buffer[Length] != 0x00 && Length < ReadLength) { Length++; }
            }

            if (Length > 0)
            {
                Value = Encoding.Default.GetString(Buffer, 0, Length);

                return true;
            }

            Value = string.Empty;

            return false;
        }

        public bool ReadByte(out byte Value)
        {
            if (Remaining < 1)
            {
                Value = 0;

                return false;
            }

            Value = PacketReader.ReadByte();

            return true;
        }

        public bool ReadBytes(byte[] Buffer)
        {
            if (Remaining < Buffer.Length) { return false; }

            PacketStream.Read(Buffer, 0, Buffer.Length);

            return true;
        }

        public bool ReadBytes(byte[] Buffer, int Length)
        {
            if (Remaining < Length)
            {
                return false;
            }

            Buffer = PacketReader.ReadBytes(Length);

            return true;
        }
        public void WriteShort(short Value) { PacketWriter.Write(Value); }
        public void WriteUShort(ushort Value) { PacketWriter.Write(Value); }
        public void WriteInt(int Value) { PacketWriter.Write(Value); }
        public void WriteUInt(uint Value) { PacketWriter.Write(Value); }
        public void WriteString(string Value) { PacketWriter.Write(Encoding.Default.GetBytes(Value)); }
        public void WriteString(string Value, bool WriteLength)
        {
            WriteByte(Convert.ToByte(Value.Length));
            WriteBytes(Encoding.Default.GetBytes(Value));
        }
        public void WriteString(string Value, int FillLength)
        {
            byte[] Buffer = Encoding.Default.GetBytes(Value);

            WriteBytes(Buffer);

            for (int Counter = 0; Counter < FillLength - Buffer.Length; Counter++) { WriteByte(0); }
        }
        public void WriteByte(byte Value) { PacketWriter.Write(Value); }
        public void WriteBytes(byte[] Value) { PacketWriter.Write(Value); }
        public void Fill(int pLength, byte pValue)
        {
            for (int i = 0; i < pLength; ++i)
            {
                WriteByte(pValue);
            }
        }
        public void ToArray(FiestaCrypto Crypto, out byte[] PacketBuffer)
        {
            byte[] BuildBuffer;
            byte[] StreamBuffer = PacketStream.ToArray();

            Crypto.Crypt(StreamBuffer, 0, StreamBuffer.Length);

            if (StreamBuffer.Length <= 0xff)
            {
                BuildBuffer = new byte[StreamBuffer.Length + 1];

                Buffer.BlockCopy(StreamBuffer, 0, BuildBuffer, 1, StreamBuffer.Length);

                BuildBuffer[0] = Convert.ToByte(StreamBuffer.Length);
            }
            else
            {
                BuildBuffer = new byte[StreamBuffer.Length + 3];

                Buffer.BlockCopy(StreamBuffer, 0, BuildBuffer, 3, StreamBuffer.Length);
                Buffer.BlockCopy(BitConverter.GetBytes(Convert.ToUInt16(StreamBuffer.Length)), 0, BuildBuffer, 1, 2);
            }

            PacketBuffer = BuildBuffer;
        }

        public void ToArray(out byte[] PacketBuffer)
        {
            byte[] BuildBuffer;
            byte[] StreamBuffer = PacketStream.ToArray();

            if (StreamBuffer.Length <= 0xff)
            {
                BuildBuffer = new byte[StreamBuffer.Length + 1];

                Buffer.BlockCopy(StreamBuffer, 0, BuildBuffer, 1, StreamBuffer.Length);

                BuildBuffer[0] = Convert.ToByte(StreamBuffer.Length);
            }
            else
            {
                BuildBuffer = new byte[StreamBuffer.Length + 3];

                Buffer.BlockCopy(StreamBuffer, 0, BuildBuffer, 3, StreamBuffer.Length);
                Buffer.BlockCopy(BitConverter.GetBytes(Convert.ToUInt16(StreamBuffer.Length)), 0, BuildBuffer, 1, 2);
            }

            PacketBuffer = BuildBuffer;
        }

        ~Packet() { Dispose(); }

        public void Dispose()
        {
            try { PacketReader.Dispose(); }
            catch { }

            try { PacketWriter.Dispose(); }
            catch { }

            try { PacketStream.Dispose(); }
            catch { }
        }
    }
}
