using System.Net;
using log4net;
using skyscraper5.Ietf.Rfc768;
using skyscraper5.Ietf.Rfc971;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Plugins;
using skyscraper8.Skyscraper.Net;
using skyscraper8.Skyscraper.Scraper;
using skyscraper8.Skyscraper.Scraper.Storage;

namespace skyscraper8.Skyscraper;

/// <summary>
/// This plugin does the opposite of what TSDuck's MpeInject does. 
/// </summary>
[SkyscraperPlugin]
public class MpeEject : ISkyscraperMpePlugin
{
    private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
    
    public void ConnectToStorage(object[] connector)
    {
        throw new NotImplementedException();
    }

    private ISubTsHandler subTsHandler;
    public void SetContext(DateTime? currentTime, object skyscraperContext)
    {
        subTsHandler = skyscraperContext as ISubTsHandler;
        if (subTsHandler == null)
        {
            logger.ErrorFormat("Couldn't get a handle for the SubTsHandler. Deencapsulation will not work.");
        }
    }

    public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
    {
        if (internetHeader.Protocol != 0x11)
            return false;

        int payloadLength = ipv4Packet.Length - 8;
        int tsCheck = payloadLength % 188;
        return tsCheck == 0;
    }

    private Dictionary<Tuple<IPEndPoint, IPEndPoint>, MpeEjectSession> sessions;
    private bool lastOutputSucessful;
    public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
    {
        if (subTsHandler == null)
        {
            lastOutputSucessful = false;
            return;
        }
        
        UserDatagram userDatagram = new UserDatagram(ipv4Packet);
        IPEndPoint sourceEndpoint = new IPEndPoint(internetHeader.SourceAddress, userDatagram.SourcePort);
        IPEndPoint targetEndpoint = new IPEndPoint(internetHeader.DestinationAddress, userDatagram.DestinationPort);
        Tuple<IPEndPoint, IPEndPoint> sessionPointer = new Tuple<IPEndPoint, IPEndPoint>(sourceEndpoint, targetEndpoint);
        
        if (sessions == null)
            sessions = new Dictionary<Tuple<IPEndPoint, IPEndPoint>, MpeEjectSession>();

        MpeEjectSession session = null;
        if (sessions.ContainsKey(sessionPointer))
        {
            session = sessions[sessionPointer];
            if (session.Bootstrapped && !session.Valid)
            {
                lastOutputSucessful = false;
                return;
            }
        }
        else
        {
            session = new MpeEjectSession(sourceEndpoint, targetEndpoint);
            sessions.Add(sessionPointer, session);
        }

        if (userDatagram.Payload.Length == 188)
        {
            if (!session.Bootstrapped)
            {
                lastOutputSucessful = false;
                return;
            }

            if (!session.Valid)
            {
                lastOutputSucessful = false;
                return;
            }

            subTsHandler.OnSubTsPacket(session,userDatagram.Payload);
            lastOutputSucessful = true;
            return;
        }

        for (int i = 0; i < userDatagram.Payload.Length; i += 188)
        {
            if (userDatagram.Payload[i] != 0x47)
            {
                if (session.Valid)
                {
                    logger.WarnFormat("Lost TS sync on stream for: {0} -> {1}", sourceEndpoint, targetEndpoint);
                }
                session.Bootstrapped = true;
                session.Valid = false;
                lastOutputSucessful = false;
                return;
            }
        }

        session.ValidDatagrams++;
        if (session.ValidDatagrams >= 10)
        {
            session.Bootstrapped = true;
            session.Valid = true;
            byte[] packetBuffer = new byte[188];
            for (int i = 0; i < userDatagram.Payload.Length; i += 188)
            {
	            if (!session.PrintedNotice)
	            {
		            logger.InfoFormat("Found a Transport Stream in IP packets: {0} -> {1}", sourceEndpoint, targetEndpoint);
		            session.PrintedNotice = true;
	            }
                Array.Copy(userDatagram.Payload, i, packetBuffer, 0, 188);
                subTsHandler.OnSubTsPacket(session, packetBuffer);
                lastOutputSucessful = true;
            }
        }
        else
        {
            lastOutputSucessful = false;
        }
    }

    public bool StopProcessingAfterThis()
    {
        return lastOutputSucessful;
    }

    
}

public class MpeEjectSession
{
    public IPEndPoint SourceEndpoint { get; }
    public IPEndPoint DestinationEndpoint { get; }

    public MpeEjectSession(IPEndPoint sourceEndpoint, IPEndPoint destinationEndpoint)
    {
        SourceEndpoint = sourceEndpoint;
        DestinationEndpoint = destinationEndpoint;
    }
    public bool Bootstrapped { get; set; }
    public bool Valid { get; set; }

    public override string ToString()
    {
        return String.Format("{0} -> {1}", SourceEndpoint, DestinationEndpoint).SanitizeFileName();
    }
    
    public int ValidDatagrams { get; set; }
    public bool PrintedNotice { get; set; }
}