using System.IO;
using skyscraper5.Dvb;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Mpeg2
{
    public class PsiDecoder : ITsPacketProcessor, IPayloadUnitDecoder
    {
        public IPsiProcessor PsiProcessor { get; }

        public PsiDecoder(int pid, IPsiProcessor psiProcessor)
        {
            PsiProcessor = psiProcessor;
            this.pid = pid;
        }

        private int pid;

        public void PushPacket(TsPacket packet)
        {
            if (packet.Payload == null)
                return;
            if (packet.PID != pid)
                return;


            bool psiJustStarted = false;
            if (psiSection == null)
            {
                if (packet.PayloadUnitStart)
                {
                    psiSection = new PsiSection();
                    psiSection.Need = 3;
                    psiSection.HeaderComplete = false;
                    psiJustStarted = true;
                }
                else
                {
                    return;
                }
            }

            MemoryStream payload = new MemoryStream(packet.Payload);
            long available = payload.Length - payload.Position;
            if (psiJustStarted)
            {
                long startOffset = packet.PayloadStartOffset;
                if (startOffset > 0)
                {
                    startOffset = payload.Position + startOffset;
                    if (startOffset > payload.Length)
                    {
                        OnPsiSectionTooLong?.Invoke(packet, psiJustStarted, psiSection);
                        PacketLoss();
                        return;
                    }
                    payload.Position += startOffset;
                    available -= startOffset;
                }
            }
            while (available > 0)
            {
                if (available > psiSection.Need)
                {
                    //Noch genug im Buffer, um need auf 0 zu bekommen.
                    byte[] temp1 = new byte[psiSection.Need];
                    if (payload.Read(temp1, 0, psiSection.Need) != psiSection.Need)
                        throw new EndOfStreamException("failed to fill psi buffer");
                    available -= psiSection.Need;
                    psiSection.Append(temp1);

                    if (!psiSection.HeaderComplete)
                    {
                        psiSection.HeaderComplete = true;
                        ushort sectionLength = psiSection.SectionLength;
                        psiSection.Need = sectionLength;
                    }
                    else
                    {
                        if (psiSection.Valid)
                        {
                            lock (this)
                            {
                                PsiProcessor.GatherPsi(psiSection, pid);
                            }
                            OnValidSection?.Invoke(psiSection, pid, available);
                        }
                        else
                        {
                            if (!DvbCrc32.ValidatePsi(psiSection))
                            {
                                OnCrcError?.Invoke(psiSection, pid,available);
                            }
                        }

                        psiSection = null;

                        bool newSection = false;
                        if (available > 0)
                        {
                            byte payloadPos = payload.ReadUInt8();
                            payload.Position--;
                            if (payloadPos != 0xff)
                                newSection = true;
                        }

                        if (newSection)
                        {
                            psiSection = new PsiSection();
                            psiSection.Need = 3;
                            psiSection.HeaderComplete = false;
                        }
                        else
                        {
                            available = 0;
                        }
                    }
                }
                else
                {
                    //Nicht mehr genug da
                    byte[] filler = payload.ReadBytes(available);
                    psiSection.Append(filler);
                    psiSection.Need -= (int)available;
                    available = 0;
                }
            }
        }

        private PsiSection psiSection;
        public void PacketLoss()
        {
            psiSection = null;
        }

        public bool HeaderComplete => psiSection.HeaderComplete;
        public int NeededBytes => psiSection.Need;

        public delegate void PsiSectionTooLong(TsPacket packet, bool justStarted, PsiSection section);

        public event PsiSectionTooLong OnPsiSectionTooLong;

        public delegate void OnSection(PsiSection section, int pid, long available);
        public event OnSection OnValidSection;
        public event OnSection OnCrcError;
    }
    
}
