using log4net;
using skyscraper5.Ietf.Rfc2460;
using skyscraper5.Ietf.Rfc971;
using skyscraper5.Skyscraper.IO;
using skyscraper8.GSE;
using skyscraper8.GSE.GSE;

namespace skyscraper8.GS.GSE_RollingSyncByte;

public class GseWithRollingSyncByteReader : IMisHandler
{
    public GsContextDto Context { get; set; }

    private ulong packetSerial;
    private GseLabel reuse;
    
    private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
    public void PushFrame(BBHeader bbHeader, ReadOnlySpan<byte> readOnlySpan)
    {
        packetSerial++;
        byte[] array = readOnlySpan.ToArray();
        MemoryStream ms = new MemoryStream(array, 0, array.Length - 4);
        while (ms.GetAvailableBytes() > 0)
        {
            byte a = ms.ReadUInt8();
            bool startIndicator = (a & 0x80) != 0;
            bool endIndicator = (a & 0x40) != 0;
            int labelTypeIndicator = (a & 0x30) >> 4;
            GsePacket gsePacket = new GsePacket(startIndicator, endIndicator, labelTypeIndicator);
            if (!startIndicator && !endIndicator && labelTypeIndicator == 0)
            {
                return;
            }
            else
            {
                byte b = ms.ReadUInt8();
                int gseLength = (a & 0x0f) << 8;
                gseLength += b;
                int gseRemain = gseLength - 2;
                if (!startIndicator || !endIndicator)
                {
                    gsePacket.FragmentId = ms.ReadUInt8();
                    gseRemain--;
                }

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

                if (startIndicator)
                {
                    gsePacket.ProtocolType = ms.ReadUInt16BE();
                    gseRemain -= 2;
                    switch (labelTypeIndicator)
                    {
                        case 0:
                            if (ms.GetAvailableBytes() < 6)
                                return; //The MAC address is sliced in half. I have my doubts that this is even allowed.
                            gsePacket.Label = new _6byteLabel(ms.ReadBytes(6));
                            reuse = gsePacket.Label;
                            gseRemain -= 6;
                            break;
                        case 1:
                            gsePacket.Label = new _3byteLabel(ms.ReadBytes(3));
                            reuse = gsePacket.Label;
                            gseRemain -= 3;
                            break;
                        case 2:
                            gsePacket.Label = BroadcastLabel.GetInstance();
                            break;
                        case 3:
                            gsePacket.Label = reuse;
                            break;
                    }
                }

                int payloadSize = gseRemain;
                if (!startIndicator && endIndicator)
                    payloadSize -= 4;

                gsePacket.GseDataBytes = ms.ReadBytes(payloadSize);
                gseRemain -= payloadSize;

                if (!startIndicator && endIndicator)
                {
                    gsePacket.Crc32 = ms.ReadUInt32BE();
                    gseRemain -= 4;
                }
            }
            
            ProcessPacket(gsePacket);
        }
    }

    private bool[] warnedEthertypes;
    private GseFragmentation[] fragments;

    public GseWithRollingSyncByteReader(GsContextDto context)
    {
        this.Context = context;
    }

    private void ProcessPacket(GsePacket gsePacket)
    {
        if (gsePacket.LabelTypeIndicator == 3 && gsePacket.Label == null)
            return;

        if (!gsePacket.StartIndicator && gsePacket.EndIndicator)
        {
            if (fragments == null)
                return;
            if (fragments[gsePacket.FragmentId.Value] == null)
                return;

            if (fragments[gsePacket.FragmentId.Value].Validate())
            {
                byte[] gseDataBytes = fragments[gsePacket.FragmentId.Value].GetGseDataBytes();
                HandleAssembledFrame(fragments[gsePacket.FragmentId.Value].ProtocolType, gseDataBytes, fragments[gsePacket.FragmentId.Value].Label);
                return;
            }
        }

        if (gsePacket.StartIndicator && gsePacket.EndIndicator)
        {
            Context.InformTrafficType(gsePacket.ProtocolType.Value);
            HandleAssembledFrame(gsePacket.ProtocolType.Value, gsePacket.GseDataBytes, gsePacket.Label);
            return;
        }

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

        if (!gsePacket.StartIndicator && !gsePacket.EndIndicator)
        {
            if (fragments == null)
                return;
            if (fragments[gsePacket.FragmentId.Value] == null)
                return;
            fragments[gsePacket.FragmentId.Value].AddFragement(gsePacket);
            return;
        }
        
        throw new NotImplementedException();
    }

    private ulong aolSerial;
    private void HandleProprietaryAolJunk(byte[] buffer, ushort protocolType)
    {
	    if (buffer[0] == 0x45)
	    {
		    Context.IpOutput.OnIpDatagram(0x0118, buffer);
		    return;
	    }
        /*aolSerial++;
        string dumpPath = String.Format("{0}{1}{2}.bin", protocolType, Path.DirectorySeparatorChar, aolSerial);
        FileInfo dumpFile = new FileInfo(dumpPath);
        dumpFile.Directory.EnsureExists();
        File.WriteAllBytes(dumpFile.FullName, buffer);*/
    }
    
    private void HandleAssembledFrame(ushort protocolType, byte[] buffer, GseLabel label)
    {
        switch (protocolType)
        {
            case 0x0002:
            case 0x0004:
                HandleProprietaryAolJunk(buffer,protocolType);
                return;
            case 0x0091:
                //according to https://www.iana.org/assignments/ule-next-headers/ule-next-headers.xhtml it's private
                return;
            case 0x0800:
                Context.IpOutput.OnIpDatagram(0x010e, buffer);
                return;
            default:
                if (warnedEthertypes == null)
                    warnedEthertypes = new bool[ushort.MaxValue];
                if (!warnedEthertypes[protocolType])
                {
                    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.",protocolType);
                    warnedEthertypes[protocolType] = true;
                }
                return;
        }
    }
}
