using log4net;
using skyscraper5.Dvb.DataBroadcasting;
using skyscraper5.Skyscraper.IO;
using skyscraper8.GS;
using skyscraper8.GSE.GSE;
using skyscraper8.Skyscraper.Scraper;
using static skyscraper5.Dvb.Descriptors.ContentIdentifierDescriptor;

namespace skyscraper8.GSE.GSE_HEM;

class GseHemReader : IMisHandler
{
    public GseHemReader(GsContextDto context)
    {
        rayBuffer = new RayBuffer();
        this.Context = context;
    }
    
    private GseLabel lastLabel;
    private RayBuffer rayBuffer;
    private const int BUFFER_THRESHOLD = 65536 * 2; //Twice the maximum PDU size of GSE. That way we can guarantee we have one full PDU in buffer.
    private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

    public GsContextDto Context { get; set; }

    public void PushFrame(BBHeader bbHeader, ReadOnlySpan<byte> readOnlySpan)
    {
        MemoryStream ms = new MemoryStream(readOnlySpan.ToArray());
        rayBuffer.Enqueue(ms, bbHeader.SyncD);

        while (rayBuffer.AvailableBytes > BUFFER_THRESHOLD)
        {
            byte a = rayBuffer.ReadUInt8();
            bool startIndicator = (a & 0x80) != 0;
            bool endIndicator = (a & 0x40) != 0;
            int labelTypeIndicator = (a & 0x30) >> 4;
            if (!startIndicator && !endIndicator)
            {
                //Padding packets probably shouldn't happen in HEM.
                rayBuffer.Resync();
                return;
            }
            else
            {
                GsePacket child = new GsePacket(startIndicator, endIndicator, labelTypeIndicator);
                
                int gseLength = (a & 0x0f) << 8;
                gseLength += rayBuffer.ReadUInt8();
                if (!startIndicator || !endIndicator)
                {
                    //According to ETSI TS 102 606-1 V1.2.1, HEM does not fragment packets. So we lost our sync!
                    rayBuffer.Resync();
                    return;
                }

                if (startIndicator && !endIndicator)
                {
                    throw new NotImplementedException("I didn't expect an open-ended GSE Packet in GSE-HEM. Please share a sample of this stream, so I can implement this.");
                }

                if (startIndicator)
                {
                    child.ProtocolType = rayBuffer.ReadUInt16BE();
                    gseLength -= 2;
                    switch (labelTypeIndicator)
                    {
                        case 0:
                            child.Label = new _6byteLabel(rayBuffer.ReadBytes(6));
                            gseLength -= 6;
                            lastLabel = child.Label;
                            break;
                        case 1:
                            child.Label = new _3byteLabel(rayBuffer.ReadBytes(3));
                            gseLength -= 3;
                            lastLabel = child.Label;
                            break;
                        case 2:
                            child.Label = BroadcastLabel.GetInstance();
                            break;
                        case 3:
                            child.Label = lastLabel;
                            break;
                        default:
                            throw new NotImplementedException(String.Format("Unknown label type indicator: {0}", labelTypeIndicator));
                    }
                }

                if (!startIndicator && endIndicator)
                    gseLength -= 4;
                
                child.GseDataBytes = rayBuffer.ReadBytes(gseLength);
                
                if (!startIndicator && endIndicator)
                    throw new NotImplementedException("I didn't expect a GSE packet with a missing head in GSE-HEM.  Please share a sample of this stream, so I can implement this.");

                HandlePacket(child);
            }
        }
    }

    private void HandlePacket(GsePacket packet)
    {
        Context.UiJunction?.EnableUiFeature(SkyscraperUiFeature.GseAnalysis);
		Context.UiJunction?.NotifyGsePacket(packet.ProtocolType.Value, packet.GseDataBytes, packet.Label);
		if (packet.StartIndicator && packet.EndIndicator)
        {
            switch (packet.ProtocolType)
            {
                case 0x0800:
                    Context.IpOutput.OnIpDatagram(0x010e,packet.GseDataBytes);
                    return;
                default:
                    logger.WarnFormat("This GSE-HEM stream contains traffic other than IP. IP is type 0x0800, but we got 0x{0:X4} here.", packet.ProtocolType);
                    return;
            }
        }
        throw new NotImplementedException(packet.ToString());
    }
}