using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Docsis
{
    internal class DocsisPacketProcessor : ITsPacketProcessor, IPayloadUnitDecoder, IDisposable
    {
        public DocsisPacketProcessor(IDocsisEventHandler eventHandler)
        {
            this.eventHandler = eventHandler;
            this.docsisEnvironment = new DocsisEnvironment(eventHandler);
        }


        private IDocsisEventHandler eventHandler;

        private bool currentPayloadUnitPresent;
        private MemoryStream currentPayloadBuffer;
        private byte? FC;

        public int? FC_TYPE
        {
            get
            {
                if (!FC.HasValue)
                    return null;

                int i = (FC.Value & 0xc0) >> 6;
                return i;
            }
        }

        public int? FC_PARM
        {
            get
            {
                if (!FC.HasValue)
                    return null;
                int i = (FC.Value & 0x3e) >> 1;
                return i;
            }
        }

        public bool? EHDR_ON
        {
            get
            {
                if (!FC.HasValue)
                    return null;

                bool i = (FC.Value & 0x01) != 0;
                return i;
            }
        }

        private byte? MAC_PARM;

        private byte? LEN_L, LEN_R;

        private int? LEN
        {
            get
            {
                if (!LEN_L.HasValue || !LEN_R.HasValue)
                    return null;

                int result = LEN_L.Value;
                result <<= 8;
                result += LEN_R.Value;
                return result;
            }
        }
        
        private byte[] EHDR;
        private bool EHDR_done;
        private int EHDR_ptr;
        private int neededBytes;

        private byte? HCS_L, HCS_R;

        private bool packetDone;

        public void PushPacket(TsPacket packet)
        {
            TsAdaptionField adaptionField = packet.AdaptionField;
            Span<byte> payload = packet.GetPayload();
            
            if (adaptionField != null || payload == null)
            {
                PacketLoss();
                return;
            }

            bool packetPayloadUnitStart = packet.PayloadUnitStart;
            MemoryStream ms = new MemoryStream(payload.ToArray(), false);

            if (!currentPayloadUnitPresent && !packetPayloadUnitStart)
                return;

            if (!currentPayloadUnitPresent && packetPayloadUnitStart)
            {
                ms.Position = packet.PID;
                currentPayloadUnitPresent = true;
                currentPayloadBuffer = new MemoryStream();
            }

            while (ms.GetAvailableBytes() > 0)
            {
                if (!FC.HasValue)
                {
                    FC = ms.ReadUInt8();
                    currentPayloadBuffer.WriteByte(FC.Value);
                    continue;
                }

                if (!MAC_PARM.HasValue)
                {
                    MAC_PARM = ms.ReadUInt8();
                    currentPayloadBuffer.WriteByte(MAC_PARM.Value);
                    continue;
                }

                if (!LEN_L.HasValue)
                {
                    LEN_L = ms.ReadUInt8();
                    currentPayloadBuffer.WriteByte(LEN_L.Value);
                    continue;
                }

                if (!LEN_R.HasValue)
                {
                    LEN_R = ms.ReadUInt8();
                    currentPayloadBuffer.WriteByte(LEN_R.Value);
                    if (EHDR_ON.Value)
                    {
                        EHDR = new byte[MAC_PARM.Value];
                        neededBytes = MAC_PARM.Value;
                        EHDR_done = false;
                    }
                    continue;
                }

                if (!EHDR_done && EHDR_ON.Value)
                {
                    int ehdrAvailable = (int)Math.Min(neededBytes, ms.GetAvailableBytes());
                    int ehdrWasRead = ms.Read(EHDR, EHDR_ptr, ehdrAvailable);
                    neededBytes -= ehdrWasRead;
                    EHDR_ptr += ehdrWasRead;
                    if (neededBytes == 0)
                    {
                        currentPayloadBuffer.Write(EHDR, 0, EHDR.Length);
                        EHDR_done = true;
                    }
                    continue;
                }

                if (!HCS_L.HasValue)
                {
                    HCS_L = ms.ReadUInt8();
                    currentPayloadBuffer.WriteByte(HCS_L.Value);
                    continue;
                }

                if (!HCS_R.HasValue)
                {
                    HCS_R = ms.ReadUInt8();
                    currentPayloadBuffer.WriteByte(HCS_R.Value);
                    packetDone = false;
                    neededBytes = LEN.Value;
                    if (EHDR_ON.Value)
                        neededBytes -= EHDR.Length;
                    continue;
                }

                if (!packetDone)
                {
                    int toRead = (int)Math.Min(neededBytes, ms.GetAvailableBytes());
                    if (toRead < 0)
                    {
                        PacketLoss();
                        return;
                    }
                    byte[] tmp = new byte[toRead];
                    int readAmount = ms.Read(tmp, 0, toRead);
                    currentPayloadBuffer.Write(tmp, 0, readAmount);
                    neededBytes -= readAmount;
                    if (neededBytes == 0)
                        packetDone = true;
                    continue;
                }

                HandlePacket();
                while (ms.GetAvailableBytes() > 0)
                {
                    byte stuffing = ms.ReadUInt8();
                    if (stuffing != 0xff)
                    {
                        ms.Position--;
                        currentPayloadUnitPresent = true;
                        currentPayloadBuffer = new MemoryStream();
                        break;
                    }
                }
            }
        }

        public void PacketLoss()
        {
            Reset();
        }

        private void Reset()
        {
            currentPayloadUnitPresent = false;
            currentPayloadBuffer = null;
            FC = null;
            MAC_PARM = null;
            LEN_L = null;
            LEN_R = null;
            EHDR = null;
            EHDR_done = false;
            EHDR_ptr = 0;
            neededBytes = 0;
            HCS_L = null;
            HCS_R = null;
            packetDone = false;
        }

        private ulong processedPackets = 0;
        private DocsisEnvironment docsisEnvironment;
        
        private void HandlePacket()
        {
            currentPayloadBuffer.Position = 0;
            docsisEnvironment.PushPacket(currentPayloadBuffer);
            Reset();
        }

        public void Dispose()
        {
            docsisEnvironment?.Dispose();
        }

        public DocsisEnvironment DocsisEnvironment => docsisEnvironment;
    }
}
