using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.T2MI.Baseband;
using skyscraper5.T2MI.Packets;
using skyscraper8.T2MI.Packets;

namespace skyscraper5.T2MI
{
    internal class T2MIDecoder : ITsPacketProcessor, IPayloadUnitDecoder
    {
        public T2MIDecoder(int pid, T2MIEventHandler t2MiEventHandler)
        {
            PrepareForNextPacket();
            this.t2MiEventHandler = t2MiEventHandler;
            this.RelatedPid = pid;
        }

        public int RelatedPid { get; private set; }

        private bool headerComplete;
        private int requiredBytes;
        private byte[] headerBytes;
        private T2MIHeader header;
        private byte[] packetBuffer;
        private long inputPacketsPushed;
        private long outputPacketsPushed;
        private T2MIEventHandler t2MiEventHandler;
        private bool firstPacketFound;

        public void PushPacket(TsPacket packet)
        {
            inputPacketsPushed++;
            bool packetPayloadUnitStart = packet.PayloadUnitStart;
            if (!firstPacketFound && packetPayloadUnitStart)
	            firstPacketFound = true;

            MemoryStream ms = new MemoryStream(packet.GetPayload().ToArray(), false);
            int partsProcessed = 0;
            byte offsetAccordingToPus = 0;
            if (packetPayloadUnitStart)
            {
                if (ms.Position == ms.Length)
                    return;
                offsetAccordingToPus = ms.ReadUInt8();
            }
            while (ms.GetAvailableBytes() > 0)
            {
                PushPart(ms, packetPayloadUnitStart, partsProcessed++, offsetAccordingToPus);
            }
        }

        private void PushPart(MemoryStream src, bool packetPayloadUnitStart, int partsProcessed, byte offsetAccordingToPUS)
        {
            if (partsProcessed == 0 && !packetPayloadUnitStart && !headerComplete && requiredBytes == 6)
            {
                //Kein Paketanfang und kein Paket was gerade zusammengebaut wird. Können wir also nicht gebrauchen
                src.Position = src.Length;
                return;
            }
            else if (partsProcessed == 0 && packetPayloadUnitStart && !headerComplete)
            {
                //Kein Aktives Paket, aber in diesem TS beginnt eins, also hinspulen.
                src.Position += offsetAccordingToPUS;
                return;
            }

            if (partsProcessed > 0 && !headerComplete && !packetPayloadUnitStart)
            {
                src.Position = src.Length;
                return;
            }
            else if ((packetPayloadUnitStart || (!packetPayloadUnitStart && requiredBytes < 6))  && !headerComplete)
            {
                //Offset schon rausgekriegt, wir müssen einen Header zusammenbauen und es beginnt ein neues Paket.
                if (headerBytes == null)
                    headerBytes = new byte[6];
                requiredBytes -= src.Read(headerBytes, 6 - requiredBytes, requiredBytes);
                if (requiredBytes == 0)
                {
                    header = new T2MIHeader(headerBytes);
                    headerComplete = true;
                    requiredBytes = header.PayloadLengthBytes;
                    packetBuffer = new byte[requiredBytes];
                }
            }
            else if (headerComplete)
            {
                int aboutToRead = requiredBytes;
                requiredBytes -= src.Read(packetBuffer, packetBuffer.Length - requiredBytes, aboutToRead);
                if (requiredBytes == 0)
                {
                    AssemblePacket(header, packetBuffer);
                    PrepareForNextPacket();
                }
            }
            else
            {
                throw new NotImplementedException("We're in a situation we didn't expect.");
            }
        }

        private bool firstPacketAssembled;
        private byte lastPacketCounter;
        private void AssemblePacket(T2MIHeader header, byte[] buffer)
        {
            Array.Resize(ref buffer, buffer.Length - 4);    //idc, lol.

            //ensure continuity
            if (firstPacketAssembled)
            {
                byte expectedPacket = lastPacketCounter;
                expectedPacket++;
                if (expectedPacket != header.PacketCount)
                {
                    t2MiEventHandler.OnT2MiPacketLoss(RelatedPid, expectedPacket, header);
                }
                lastPacketCounter = header.PacketCount;
            }
            else
            {
                lastPacketCounter = header.PacketCount;
                firstPacketAssembled = true;
            }
            
            T2MiPacket t2MiPacket = DecodePacket(header, buffer);
            if (t2MiPacket == null)
            {
                t2MiEventHandler.OnT2MiError(RelatedPid, 2);
                return;
            }

            if (!t2MiPacket.Valid)
            {
                t2MiEventHandler.OnT2MiError(RelatedPid, 1);
                return;
            }

            switch (t2MiPacket.Header.PacketType)
            {
                case 0x00:
                    _0x00_BasebandFrame basebandFrame = (_0x00_BasebandFrame)t2MiPacket;
                    DecodeBasebandFrame(basebandFrame);
                    break;
                case 0x01:
                    _0x01_IqData iqData2 = (_0x01_IqData)t2MiPacket;
                    t2MiEventHandler.OnT2MiIqData(RelatedPid, iqData2);
                    break;
                case 0x02:
                    _0x02_ArbitraryCellInsertion arbitraryCellInsertion = (_0x02_ArbitraryCellInsertion)t2MiPacket;
                    t2MiEventHandler.OnT2MiArbitraryCellInsertion(RelatedPid, arbitraryCellInsertion);
                    break;
                case 0x10:
                    _0x10_L1Current l1Current = (_0x10_L1Current)t2MiPacket;
                    t2MiEventHandler.OnT2MiL1Current(RelatedPid, l1Current);
                    break;
                case 0x11:
                    _0x11_L1Future l1future = (_0x11_L1Future)t2MiPacket;
                    t2MiEventHandler.OnT2MiL1Future(RelatedPid, l1future);
                    break;
                case 0x12:
                    _0x12_P2BiasBalancingCells p2BiasBalancingCells = (_0x12_P2BiasBalancingCells)t2MiPacket;
                    t2MiEventHandler.OnT2MiBalancingCells(RelatedPid, p2BiasBalancingCells.FrameIndex, p2BiasBalancingCells.NumActiveBiasCellsPerP2);
                    break;
                case 0x20:
                    _0x20_DvbT2Timestamp t2Timestamp = (_0x20_DvbT2Timestamp)t2MiPacket;
                    t2MiEventHandler.OnT2MiTimestamp(RelatedPid, t2Timestamp);
                    break;
                case 0x21:
                    _0x21_IndividualAddressing individualAddressing = (_0x21_IndividualAddressing)t2MiPacket;
                    t2MiEventHandler.OnT2MiIndividualAddressing(RelatedPid, individualAddressing);
                    break;
                case 0x31:
                    _0x31_IqData iqData = (_0x31_IqData)t2MiPacket;
                    t2MiEventHandler.OnT2MiIqData(RelatedPid, iqData);
                    break;
                case 0x30:
                    //Null. Nothing happens here.
                    break;
                case 0x33:
                    _0x33_Subpart subpart = (_0x33_Subpart)t2MiPacket;
                    if (subpart.PreferDiscard)
                    {
	                    t2MiEventHandler.OnT2MiError(RelatedPid, 3);
	                    return;
					}
                    switch (subpart.SubpartVariety)
                    {
                        case 0:
                            break;
                        default:
                            if (subpart.Subpart == null)
                                break;
                            throw new NotImplementedException(subpart.Subpart.ToString());
                    }
                    break;
                case 0xf0:
                    _0xF0_FramingTimingInformation fti = (_0xF0_FramingTimingInformation)t2MiPacket;
                    t2MiEventHandler.OnFramingAndTimingInformation(RelatedPid, fti);
                    break;
                default:
                    throw new NotImplementedException(t2MiPacket.GetType().Name);
            }
        }

        private void DecodeBasebandFrame(_0x00_BasebandFrame basebandFrame)
        {
            if (basebandFrame.BasebandFrame == null)
                return;

            if (basebandFrame.BasebandFrame.Length < 10)
                return;

            if (basebandPacketInProgress == null)
            {
                basebandPacketInProgress = new bool[256];
                basebandPacket = new byte[256][];
                basebandPacketOffset = new int[256];
            }

            byte plp = basebandFrame.PlpId;

            MemoryStream ms = new MemoryStream(basebandFrame.BasebandFrame, false);

            byte readUInt8 = ms.ReadUInt8();    // t2packet[9]
            T2BasebandStreamType streamType = (T2BasebandStreamType)((readUInt8 & 0xc0) >> 6);
            bool isSingle = (readUInt8 & 0x20) != 0;
            bool isCcm = (readUInt8 & 0x10) != 0;
            bool issyi = (readUInt8 & 0x08) != 0;
            bool npd = (readUInt8 & 0x04) != 0;
            int ext = (readUInt8 & 0x03);

            byte matype2 = ms.ReadUInt8();      // t2packet[10]
            ushort upl = ms.ReadUInt16BE();     // t2packet[11]
            ushort dfl = ms.ReadUInt16BE();     // t2packet[13]
            byte sync = ms.ReadUInt8();         // t2packet[15]
            ushort syncD = ms.ReadUInt16BE();   // t2packet[16]
            byte crc8mode = ms.ReadUInt8();     // t2packet[18]

            int uplBytes = upl / 8;
            int syncDBytes = syncD / 8;
            int dflBytes = dfl / 8;

            if (!basebandPacketInProgress[plp] && syncD == 65535)
            {
                //Kein Frame in Progress und es beginnt auch kein neuer, also weg damit.
                return;
            }

           
            int expectedLength = (int)ms.Position + dflBytes;
            if (expectedLength > basebandFrame.BasebandFrame.Length)
                return;

            MemoryStream ms2 = new MemoryStream(basebandFrame.BasebandFrame, (int)ms.Position, dflBytes, false);
            if (!basebandPacketInProgress[plp] && syncD != 65535)
            {
                ms2.Position += syncDBytes;
            }
            while (ms2.GetAvailableBytes() > 0)
            {
                if (basebandPacket[plp] == null)
                {
                    basebandPacket[plp] = new byte[188];
                    basebandPacket[plp][0] = 0x47;
                    basebandPacketOffset[plp] = 1;
                    basebandPacketInProgress[plp] = true;
                    continue;
                }

                if (basebandPacket[plp] != null)
                {
                    basebandPacketOffset[plp] += ms2.Read(basebandPacket[plp], basebandPacketOffset[plp], 188 - basebandPacketOffset[plp]);
                    if (basebandPacketOffset[plp] == 188)
                    {
                        t2MiEventHandler.OnT2MiPacket(RelatedPid, basebandFrame.PlpId, basebandPacket[plp]);
                        basebandPacketInProgress[plp] = false;
                        basebandPacket[plp] = null;
                        basebandPacketOffset[plp] = 0;
                        outputPacketsPushed++;
                    }
                    continue;
                }
            }
        }

        private bool[] basebandPacketInProgress;
        private byte[][] basebandPacket;
        private int[] basebandPacketOffset;

        #region Reflection für die einzelnen Pakettypen

        private static ConstructorInfo[] packetConstructorInfos;

        private T2MiPacket DecodePacket(T2MIHeader header, byte[] buffer)
        {
            if (header.PacketType != 0xf0)
            {
                if (header.PacketType > 0x33 || header.PacketType == 0x03 || header.PacketType == 0x0f || header.PacketType == 0x19 || header.PacketType == 0x18 ||
                    header.PacketType == 0x0a || header.PacketType == 0x2e || header.PacketType == 0x2b || header.PacketType == 0x1e || header.PacketType == 0x05 ||
                    header.PacketType == 0x1f || header.PacketType == 0x1d || header.PacketType == 0x1c || header.PacketType == 0x06 || header.PacketType == 0x25 ||
                    header.PacketType == 0x28 || header.PacketType == 0x08 || header.PacketType == 0x24 || header.PacketType == 0x17 || header.PacketType == 0x2a ||
                    header.PacketType == 0x23 ||
                    header.PacketType == 0x15 ||
                    header.PacketType == 0x0e ||
                    header.PacketType == 0x2f ||
                    header.PacketType == 0x2d ||
                    header.PacketType == 0x04 ||
                    header.PacketType == 0x26 ||
                    header.PacketType == 0x22 ||
                    header.PacketType == 0x0d ||
                    header.PacketType == 0x13 ||
                    header.PacketType == 0x29 ||
                    header.PacketType == 0x09 ||
                    header.PacketType == 0x0b ||
                    header.PacketType == 0x2c ||
                    header.PacketType == 0x1a ||
                    header.PacketType == 0x16 ||
                    header.PacketType == 0x1b ||
                    header.PacketType == 0x14 ||
                    header.PacketType == 0x27 ||
                    header.PacketType == 0x0c ||
                    header.PacketType == 0x07)
                {
                    //reserved for future use
                    return null;
                }
            }

            if (packetConstructorInfos == null)
                LoadPacketTypes();

            ConstructorInfo constructorInfo = packetConstructorInfos[header.PacketType];
            if (constructorInfo == null)
                throw new NotImplementedException(String.Format("T2-MI packet type 0x{0:X2}", header.PacketType));

            object invoke = constructorInfo.Invoke(new object[] { header, buffer });
            return (T2MiPacket) invoke;
        }

        private static void LoadPacketTypes()
        {
	        packetConstructorInfos = PluginManager.GetInstance().GetT2MiPacketTypes();
	        bool invalid = packetConstructorInfos.All(x => x == null);
            if (invalid)
	            throw new T2MiException("No Packet Types loaded");
		}
        
        #endregion

        public void PacketLoss()
        {
            PrepareForNextPacket();
            basebandPacketInProgress = null;
            basebandPacket = null;
            basebandPacketOffset = null;
        }

        private void PrepareForNextPacket()
        {
            headerComplete = false;
            requiredBytes = 6;
            headerBytes = null;
            header = null;
            packetBuffer = null;
        }

    }
}
