using log4net;
using skyscraper5.Dvb.DataBroadcasting;
using skyscraper5.Skyscraper.IO;
using skyscraper8.GS;
using skyscraper8.Skyscraper.Scraper;

namespace skyscraper8.GSE.GSE;

internal class GseReader : IMisHandler
{
    public GseReader(GsContextDto dto)
    {
        this.Context = dto;
    }
    
    private GseLabel lastLabel;
    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());

        while (ms.Position < ms.Length)
        {
            byte a = ms.ReadUInt8();
            bool startIndicator = (a & 0x80) != 0;
            bool endIndicator = (a & 0x40) != 0;
            int labelTypeIndicator = (a & 0x30) >> 4;
            if (!startIndicator && !endIndicator && labelTypeIndicator == 0)
            {
                //end of BBFrame
                return;
            }
            else
            {
                int gseLength = (a & 0x0f);
                gseLength <<= 8;
                gseLength += ms.ReadUInt8();

                GsePacket child = new GsePacket(startIndicator, endIndicator, labelTypeIndicator);
                if (!startIndicator || !endIndicator)
                {
                    if (ms.Position == ms.Length)
                        return;
                    child.FragmentId = ms.ReadUInt8();
                    gseLength--;
                }

                if (startIndicator && !endIndicator)
                {
                    child.TotalLength = ms.ReadUInt16BE();
                    gseLength -= 2;
                }

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

                if (!startIndicator && endIndicator)
                    gseLength -= 4; //for crc32

                if (gseLength > ms.GetAvailableBytes())
                    return;
                if (gseLength <= -1)
                    return;
                child.GseDataBytes = ms.ReadBytes(gseLength);

                if (!startIndicator && endIndicator)
                    child.Crc32 = ms.ReadUInt32BE();
                
                ProcessPacket(child);
            }
        }
    }

    private bool[] warnedEthertypes;
    private GseFragmentation[] fragmentations;
    private void ProcessPacket(GsePacket child)
    {
        if (!child.StartIndicator && child.EndIndicator)
        {
            if (fragmentations == null)
                return;
            GseFragmentation currentFragmentation = fragmentations[child.FragmentId.Value];
            if (currentFragmentation == null)
                return;
            currentFragmentation.AddFragement(child);
            if (currentFragmentation.Validate())
            {
                Context.IpOutput.OnIpDatagram(0x010e,currentFragmentation.GetGseDataBytes());
            }

            fragmentations[child.FragmentId.Value] = null;
            return;
        }

        if (child.StartIndicator && !child.EndIndicator)
        {
            if (fragmentations == null)
                fragmentations = new GseFragmentation[256];
            fragmentations[child.FragmentId.Value] = new GseFragmentation(child);
            return;
        }

        if (!child.StartIndicator && !child.EndIndicator)
        {
            if (fragmentations == null)
            {
                //We haven't seen any starting frames yet, so we can't use an inbetween frame. We can throw this away.
                return;
            }
            if (fragmentations[child.FragmentId.Value] == null)
            {
                //fragment beginning missing, we can throw this away.
                return;
            }
            throw new NotImplementedException(child.ToString());
        }

		Context.UiJunction?.EnableUiFeature(SkyscraperUiFeature.GseAnalysis);
		Context.UiJunction?.NotifyGsePacket(child.ProtocolType.Value, child.GseDataBytes, child.Label);
        if (child.StartIndicator && child.EndIndicator)
        {
            switch (child.ProtocolType)
            {
                case 0x0800:
                    Context.IpOutput.OnIpDatagram(0x010e, child.GseDataBytes);
                    break;
                default:
                    if (warnedEthertypes == null)
                        warnedEthertypes = new bool[ushort.MaxValue];
                    if (!warnedEthertypes[child.ProtocolType.Value])
                    {
                        logger.WarnFormat("This GSE contains other traffic types (type 0x{0:X4}) besides IP (type 0x0800). If it's not too much trouble, please consider submitting a sample.",child.ProtocolType.Value);
                        warnedEthertypes[child.ProtocolType.Value] = true;
                    }
                    break;
            }

            return;
        }
        throw new NotImplementedException(child.ToString());
    }
} 