using log4net;
using skyscraper5.Mpeg2;
using skyscraper8.GS;
using skyscraper8.GS.GSE_BFBS;
using skyscraper8.GS.GSE_RollingSyncByte;
using skyscraper8.GS.SiminnRadiomidun;
using skyscraper8.GSE.GSE_HEM;
using skyscraper8.GSE.GSE;

namespace skyscraper8.GSE;

class MisHandlerProxy : IMisHandler
{
	public GsContextDto Context { get; set; }
	private readonly ILog logger;
    private HashSet<Tuple<int, byte>> _postedStreamTypes;

    public MisHandlerProxy(GsContextMisDto context)
    {
        this.logger = LogManager.GetLogger(String.Format("{0} MIS {1}",System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name,context.Mis));
        this.Context = context;
    }
    
    private int state;
    private Queue<Tuple<BBHeader, byte[]>> waitingPackets;
    private DateTime lastWaitingTimePrinted;
    private IMisHandler misHandler;
    private bool unknownStateError;
    

    public void PushFrame(BBHeader bbHeader, ReadOnlySpan<byte> readOnlySpan)
    {
        if (readOnlySpan.Length == 0)
        {
            return;
        }

        switch (state)
        {
	        case 0:	//No packets collected yet.
		        waitingPackets = new Queue<Tuple<BBHeader, byte[]>>();
		        logger.InfoFormat("Attempting to detect the stream type of MIS {0}...",bbHeader.Matype2);
		        waitingPackets.Enqueue(new Tuple<BBHeader, byte[]>(bbHeader, readOnlySpan.ToArray()));
		        lastWaitingTimePrinted = DateTime.Now;
		        state = 1;
		        break;
	        case 1:
		        DateTime currentTime = DateTime.Now;
		        if (currentTime - lastWaitingTimePrinted > TimeSpan.FromSeconds(1))
		        {
			        logger.InfoFormat("Waiting for {0} more packets on MIS {1}...",100 - waitingPackets.Count,bbHeader.Matype2);
			        lastWaitingTimePrinted = currentTime;
		        }
		        waitingPackets.Enqueue(new Tuple<BBHeader, byte[]>(bbHeader, readOnlySpan.ToArray()));
		        if (waitingPackets.Count == 100)
		        {
			        state = 2;
		        }
		        break;
	        case 2:
		        RunDetection();
		        DrainQueue();
		        misHandler.PushFrame(bbHeader, readOnlySpan);
		        state = 3;
		        break;
	        case 3:
				try
				{
					misHandler.PushFrame(bbHeader, readOnlySpan);
				}
				catch (Exception e)
				{
                    logger.ErrorFormat("An error occurred while handling a GS packet. This means that the stream type autodetection guessed wrong.");
					OnUnknownStreamFormat();
                }
		        break;
	        default:
		        if (!unknownStateError)
		        {
			        logger.FatalFormat("The Finite-state machine of the MIS Handler reached an impossible state. This is a bug in skyscraper8. It would be great if you could share a sample of this stream to reproduce this, so I can fix this.");
			        unknownStateError = true;
		        }
		        break;
        }
    }

    private enum DetectedGsType
    {
	    ContinuousGSE,
	    GSE_HEM,
	    SimminRadiomidun,
	    BfbsGSE,
	    GseWithRollingSyncByte
    }

    private void SucessfulDetection(DetectedGsType detectedGsType)
    {
	    logger.InfoFormat("MIS {0} looks like {1}, will try to treat it as such.",Context.GetMisId(),detectedGsType);
	    switch (detectedGsType)
	    {
		    case DetectedGsType.ContinuousGSE: misHandler = new GseReader(Context); break;
		    case DetectedGsType.GSE_HEM: misHandler = new GseHemReader(Context); break;
		    case DetectedGsType.SimminRadiomidun: misHandler = new SiminnRadiomidunReader(Context); break;
		    case DetectedGsType.BfbsGSE:  misHandler = new BfbsGseReader(Context); break;
		    case DetectedGsType.GseWithRollingSyncByte: misHandler = new GseWithRollingSyncByteReader(Context); break;
		    default:
			    logger.FatalFormat("However, {0} is not implemented yet, sorry...", detectedGsType);
			    misHandler = new NullMisHandler();
			    break;
	    }
    }
    
    private bool RunDetection()
    {
	    //bbHeader.TsGs == 1 && bbHeader.SyncByte == 0
	    int continuousGseScore = waitingPackets.Count(x => x.Item1.TsGs == 1 && x.Item1.SyncByte == 0);
	    if (continuousGseScore == 100)
	    {
		    SucessfulDetection(DetectedGsType.ContinuousGSE);
		    return true;
	    }
	    
	    //bbHeader.TsGs == 2 && bbHeader.SyncByte == 0
	    int gseHemScore = waitingPackets.Count(x => x.Item1.TsGs == 2 && x.Item1.SyncByte == 0);
	    if (gseHemScore == 100)
	    {
		    SucessfulDetection(DetectedGsType.GSE_HEM);
		    return true;
	    }
	    
	    //bbHeader.TsGs == 0 && bbHeader.SyncByte == 71 && bbHeader.UserPacketLength == 188
	    int simminRadiomidunScore = waitingPackets.Count(x => x.Item1.TsGs == 0 && x.Item1.SyncByte == 71 && x.Item1.UserPacketLength == 188);
	    if (simminRadiomidunScore == 100)
	    {
		    SucessfulDetection(DetectedGsType.SimminRadiomidun);
		    return true;
	    }
	    
	    //bbHeader.TsGs == 1 && bbHeader.SyncByte == 1 && bbHeader.UserPacketLength == 0
	    int bfbsGseScore = waitingPackets.Count(x => x.Item1.TsGs == 1 && x.Item1.SyncByte == 1 && x.Item1.UserPacketLength == 0);
	    if (bfbsGseScore == 100)
	    {
		    SucessfulDetection(DetectedGsType.BfbsGSE);
		    return true;
	    }

	    bool allPacketsHaveValidCrc32 = AnalyzeCrc32Values();

	    if (!allPacketsHaveValidCrc32)
	    {
		    if (StatisticalAnalysis_Ses12_11476v())
		    {
			    SucessfulDetection(DetectedGsType.GseWithRollingSyncByte);
			    return true;
		    }
	    }

        OnUnknownStreamFormat();
        return false;
    }

    private void DrainQueue()
    {
		try
		{
            while (waitingPackets.Count > 0)
            {
                Tuple<BBHeader, byte[]> dequeue = waitingPackets.Dequeue();
                misHandler.PushFrame(dequeue.Item1, dequeue.Item2);
            }
            waitingPackets = null;
        }
	    catch (Exception ex)
		{
			logger.ErrorFormat("While trying to handle the buffered GS packets, an error occurred. This means that the stream type autodetection guessed wrong.");
			OnUnknownStreamFormat();
			DrainQueue();
        }
    }

	private void OnUnknownStreamFormat()
	{
        logger.WarnFormat("MIS {0} has an unknown stream type. The IP packet detection algorithm from older skyscraper8 versions will be used. For further analysis, please consider submitting a sample of this stream.", Context.GetMisId());
        misHandler = new IPv4PacketBruteforce(Context);
    }

    private bool AnalyzeCrc32Values()
    {
	    foreach (Tuple<BBHeader, byte[]> waitingPacket in waitingPackets)
	    {
		    bool validateCrc = DvbCrc32.ValidateCrc(waitingPacket.Item2);
		    if (!validateCrc)
			    return false;
	    }

	    return true;
    }
    
    private bool StatisticalAnalysis_Ses12_11476v()
    {
	    byte[] syncBytes = new byte[256];
		foreach (Tuple<BBHeader, byte[]> dequeue in waitingPackets)
		{
			syncBytes[dequeue.Item1.SyncByte]++;
			if (syncBytes[dequeue.Item1.SyncByte] > 3)
				return false;
			
			if (dequeue.Item1.DataFieldLength != dequeue.Item2.Length)
				return false;
		}

		return true;
    }
}