using System;
using System.Text;
using System.Windows.Forms;

namespace MapleShark
{
	public sealed class FiestaPacket: ListViewItem {
        // Fields
	    public Definition Definition;
        public byte Header { get; }
        public byte Type { get; }

        // Methods
		internal FiestaPacket(DateTime pTimestamp, bool pOutbound, ushort pOpcode, string pName, byte[] pBuffer, DateTime pStreamCreated)
			: this(pTimestamp, pOutbound, Convert.ToByte(pOpcode >> 10), Convert.ToByte(pOpcode & 1023), pName, pBuffer, pStreamCreated) {

		}
        internal FiestaPacket(DateTime pTimestamp, bool pOutbound, byte pHeader, byte pType, string pName, byte[] pBuffer, DateTime pStreamCreated)
            : base(new[] {
              ((pTimestamp - pStreamCreated).Ticks / 10000).ToString(),
                pOutbound ? "Outbound" : "Inbound",
                pBuffer.Length.ToString(),
		            $"{pHeader:D3} | {pType:D3}",
                pName }) {
            Timestamp = pTimestamp;
            Outbound = pOutbound;
			Opcode = Convert.ToUInt16((pHeader << 10) + (pType & 1023));
            Header = pHeader;
            Type = pType;
            InnerBuffer = pBuffer;

            Definition = Config.Instance.Definitions.Find(d => d.Outbound == Outbound && d.Opcode == Opcode);
        }

        public byte[] Dump() {
            var dst = new byte[InnerBuffer.Length + 12];
            var length = (ushort)InnerBuffer.Length;
            if( Outbound ) {
                length = (ushort)(length | 0x8000);
            }
            var ticks = Timestamp.Ticks;
            dst[0] = (byte)ticks;
            dst[1] = (byte)(ticks >> 8);
            dst[2] = (byte)(ticks >> 0x10);
            dst[3] = (byte)(ticks >> 0x18);
            dst[4] = (byte)(ticks >> 0x20);
            dst[5] = (byte)(ticks >> 0x28);
            dst[6] = (byte)(ticks >> 0x30);
            dst[7] = (byte)(ticks >> 0x38);
            dst[8] = (byte)length;
            dst[9] = (byte)(length >> 8);
            dst[10] = (byte)Opcode;
            dst[11] = (byte)(Opcode >> 8);
            Buffer.BlockCopy(InnerBuffer, 0, dst, 12, InnerBuffer.Length);
            return dst;
        }

        public bool ReadByte(out byte pValue) {
            pValue = 0;
            if( Cursor + 1 > InnerBuffer.Length ) {
                return false;
            }
            pValue = InnerBuffer[Cursor++];
            return true;
        }

        public bool ReadBytes(byte[] pBytes) {
            return ReadBytes(pBytes, 0, pBytes.Length);
        }

        public bool ReadBytes(byte[] pBytes, int pStart, int pLength) {
            if( Cursor + pLength > InnerBuffer.Length ) {
                return false;
            }
            Buffer.BlockCopy(InnerBuffer, Cursor, pBytes, pStart, pLength);
            Cursor += pLength;
            return true;
        }

        public bool ReadDouble(out double pValue) {
            pValue = 0.0;
            if( Cursor + 8 > InnerBuffer.Length ) {
                return false;
            }
            pValue = BitConverter.ToDouble(InnerBuffer, Cursor);
            Cursor += 8;
            return true;
        }

        public bool ReadFloat(out float pValue) {
            pValue = 0f;
            if( Cursor + 4 > InnerBuffer.Length ) {
                return false;
            }
            pValue = BitConverter.ToSingle(InnerBuffer, Cursor);
            Cursor += 4;
            return true;
        }

        public bool ReadInt(out int pValue) {
            pValue = 0;
            if( Cursor + 4 > InnerBuffer.Length ) {
                return false;
            }
            pValue = InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8) | (InnerBuffer[Cursor++] << 0x10) | (InnerBuffer[Cursor++] << 0x18);
            return true;
        }

        public bool ReadLong(out long pValue) {
            pValue = 0L;
            if( Cursor + 8 > InnerBuffer.Length ) {
                return false;
            }
            pValue = InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8) | (InnerBuffer[Cursor++] << 0x10) | (InnerBuffer[Cursor++] << 0x18) | InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8) | (InnerBuffer[Cursor++] << 0x10) | (InnerBuffer[Cursor++] << 0x18);
            return true;
        }

        public bool ReadPaddedString(out string pValue, int pLength) {
            pValue = "";
            if( Cursor + pLength > InnerBuffer.Length ) {
                return false;
            }
            var count = 0;
            while( count < pLength && InnerBuffer[Cursor + count] != 0 ) {
                count++;
            }
            if( count > 0 ) {
                pValue = Encoding.ASCII.GetString(InnerBuffer, Cursor, count);
            }
            Cursor += pLength;
            return true;
        }

        public bool ReadSByte(out sbyte pValue) {
            pValue = 0;
            if( Cursor + 1 > InnerBuffer.Length ) {
                return false;
            }
            pValue = (sbyte)InnerBuffer[Cursor++];
            return true;
        }

        public bool ReadShort(out short pValue) {
            pValue = 0;
            if( Cursor + 2 > InnerBuffer.Length ) {
                return false;
            }
            pValue = (short)(InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8));
            return true;
        }

        public bool ReadUInt(out uint pValue) {
            pValue = 0;
            if( Cursor + 4 > InnerBuffer.Length ) {
                return false;
            }
            pValue = (uint)(InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8) | (InnerBuffer[Cursor++] << 0x10) | (InnerBuffer[Cursor++] << 0x18));
            return true;
        }

        public bool ReadULong(out ulong pValue) {
            pValue = 0L;
            if( Cursor + 8 > InnerBuffer.Length ) {
                return false;
            }
            pValue = (ulong)(InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8) | (InnerBuffer[Cursor++] << 0x10) | (InnerBuffer[Cursor++] << 0x18) | InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8) | (InnerBuffer[Cursor++] << 0x10) | (InnerBuffer[Cursor++] << 0x18));
            return true;
        }

        public bool ReadUShort(out ushort pValue) {
            pValue = 0;
            if( Cursor + 2 > InnerBuffer.Length ) {
                return false;
            }
            pValue = (ushort)(InnerBuffer[Cursor++] | (InnerBuffer[Cursor++] << 8));
            return true;
        }

        public void Rewind() {
            Cursor = 0;
        }

        // Properties
        public int Cursor { get; private set; }

	    public byte[] InnerBuffer { get; }

	    public int Length => InnerBuffer.Length;

	    public string DefinitionName {
            set => SubItems[4].Text = value;
	    }

        public ushort Opcode { get; }

	    public bool Outbound { get; }

	    public int Remaining => InnerBuffer.Length - Cursor;

	    public DateTime Timestamp { get; }

	    public void ReverseCursor(int pLength) {
            Cursor -= pLength;
        }
    }
}
