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

namespace FilterAPI.Networking
{
    public class Packet : IDisposable
    {
        private MemoryStream PacketStream;
        public BinaryWriter PacketWriter;
        public BinaryReader PacketReader;

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

        public UInt16 OPCode;
        public Byte Header;
        public Byte Type;

        public Packet(Byte[] Buffer)
        {
            PacketStream = new MemoryStream(Buffer);
            PacketReader = new BinaryReader(PacketStream, Encoding.Default, false);
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (PacketReader != null)
                    {
                        PacketReader.Close();
                        PacketReader.Dispose();
                    }

                    if (PacketWriter != null)
                    {
                        PacketWriter.Close();
                        PacketWriter.Dispose();
                    }

                    if (PacketStream != null)
                    {
                        PacketStream.Close();
                        PacketStream.Dispose();
                    }
                }

                disposed = true;
            }
        }

        public void Dispose() { Dispose(true); }

        ~Packet() { Dispose(false); }

        public Boolean SetHeaderAndType()
        {
            if (disposed) { return false; }

            try
            {
                OPCode = PacketReader.ReadUInt16();

                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, Encoding.Default, false);

            Header = TransferHeader;
            Type = TransferType;

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

            PacketWriter.Write((UInt16)OPCode);
        }

        public String ReadString(Int32 ReadLength)
        {
            if (disposed) { return String.Empty; }
            else if (Remaining < ReadLength) { return String.Empty; }

            Byte[] Buffer = new Byte[ReadLength];

            if (!ReadBytes(Buffer)) { return String.Empty; }

            Int32 Length = 0;

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

            if (Length > 0) { return Encoding.Default.GetString(Buffer, 0, Length); }

            return String.Empty;
        }

        public Boolean ReadBytes(Byte[] Buffer)
        {
            if (disposed) { return false; }
            else if (Remaining < Buffer.Length) { return false; }

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

            return true;
        }

        public void WriteString(String Value)
        {
            if (disposed) { return; }

            PacketWriter.Write(Encoding.Default.GetBytes(Value));
        }

        public void WriteString(String Value, Int32 FillLength)
        {
            if (disposed) { return; }

            Byte[] Buffer = Encoding.Default.GetBytes(Value);

            PacketWriter.Write(Buffer);

            for (Int32 Counter = 0; Counter < FillLength - Buffer.Length; Counter++) { PacketWriter.Write((Byte)0); }
        }

        public void WriteBytes(Byte[] Value)
        {
            if (disposed) { return; }

            PacketWriter.Write(Value);
        }

        public void ToArray(out Byte[] PacketBuffer)
        {
            if (disposed)
            {
                PacketBuffer = new Byte[0];

                return;
            }

            try
            {
                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;
            }
            catch { PacketBuffer = new Byte[0]; }
        }

        public void ToArray(FiestaCrypto Crypto, out Byte[] PacketBuffer)
        {
            if (disposed)
            {
                PacketBuffer = new Byte[1];

                return;
            }

            try
            {
                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;
            }
            catch { PacketBuffer = new Byte[1]; }
        }
    }
}
