using log4net;
using skyscraper5.Skyscraper;
using skyscraper5.src.Mpeg2.PacketFilter;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;

namespace skyscraper5.Mpeg2
{
    public class TsContext : ITsPacketProcessor
    {
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
		private ITsPacketProcessor[] processors;
        private uint[] continuities;
        private ulong[] pidPackets;
        private ulong[] scrambledPackets;
        private ulong[] teiPackets;

		public long PacketLossEvents { get; private set; }
        public bool FirstPacketDone { get; private set; }
        public long PacketsRead { get; private set; }
        public long TheoreticalOffset { get; private set; }
        public List<IPacketFilter> FilterChain { get; set; }
        public bool LikelyBrokenEncapsulation => this.encapsulationBrokenConfirmed;

        public void PushPacket(TsPacket packet)
        {
            if (TcpProxyEnabled && tcpTsProxy != null)
                tcpTsProxy.PushPacket(packet);

            InjectIntoPcrMonitor(packet);

            if (!FirstPacketDone)
            {
                FirstPacketDone = true;
                pidPackets = new ulong[0x2000];
            }
            else
            {
                PacketsRead++;
                TheoreticalOffset += 188;
            }

            pidPackets[packet.PID]++;

			if (FilterChain == null || FilterChain.Count == 0)
				throw new InvalidOperationException("The filter chain has not been initialized.");

			if (packet.TSC != 0)
			{
				if (scrambledPackets == null)
					scrambledPackets = new ulong[0x2000];
				scrambledPackets[packet.PID]++;
			}

			if (packet.TEI)
			{
				if (teiPackets == null)
					teiPackets = new ulong[0x2000];
				teiPackets[packet.PID]++;
			}
			CheckBrokenEncapsulation(packet);

			for (int i = 0; i < FilterChain.Count; i++)
			{
                if (FilterChain[i] == null)
                    continue;

				if (!FilterChain[i].PassPacket(packet))
					return;
			}

			EnsureContinuity(packet);

			if (processors == null)
                return;

            if (processors[packet.PID] == null)
                return;

            processors[packet.PID].PushPacket(packet);
        }

        private bool encapsulationBrokenConfirmed;
        private bool CheckBrokenEncapsulation(TsPacket packet)
        {
	        if (encapsulationBrokenConfirmed)
		        return true;

	        switch (packet.PID)
	        {
                case 0x0000:
                case 0x0001:
	                break;
                default:
	                return false;
	        }

	        int occupiedPids = CountOccupiedPids();
	        if (occupiedPids < 1000)
		        return false;

	        if (packet.TSC == 0)
		        return false;

            logger.WarnFormat("It looks like you're either having bad reception or this is a GS with broken encapsulation. This means that you either have FrameMode enabled, or are not using a STiD135.");
            encapsulationBrokenConfirmed = true;
            return true;
        }

        private int CountOccupiedPids()
        {
	        int result = 0;
	        for (int i = 0; i < pidPackets.Length; i++)
	        {
		        if (pidPackets[i] > 0)
			        result++;
	        }
	        return result;
        }

        private bool EnsureContinuity(TsPacket packet)
        {
            //Found in ISO 13818-1.pdf, page 38
            if (packet.GetPayload() == null)
                return true;

            if (packet.PID == 0x1fff)
	            return true;

            uint pid = packet.PID;
            if (continuities[pid] == UInt32.MaxValue)
            {
                continuities[pid] = packet.Continuity;
            }
            else
            {
                uint oldContinuity = continuities[pid];
                uint newContinuity = packet.Continuity;
                if (newContinuity == (oldContinuity + 1) || (oldContinuity == 15 && newContinuity == 0))
                {
                    continuities[pid] = packet.Continuity;
                }
                else
                {
                    continuities[pid] = UInt32.MaxValue;
                    IPayloadUnitDecoder payloadUnitDecoder = processors[pid] as IPayloadUnitDecoder;
                    if (payloadUnitDecoder != null)
                        payloadUnitDecoder.PacketLoss();
                    PacketLossEvents++;
                    OnPacketLoss?.Invoke(pid, oldContinuity, newContinuity, PacketLossEvents);
                    return false;
                }
            }

            return true;
        }

        private DateTime packetLossMeasPrev, packetLossMeasCurr;
        private ulong packetLossMeas;

        public void PushPacket(byte[] buffer)
        {
            PushPacket(new TsPacket(buffer));
        }

        public bool RegisterPacketProcessor(int pid, ITsPacketProcessor packageProcessor)
        {
            if (processors == null)
            {
                processors = new ITsPacketProcessor[8192];
                continuities = new uint[8192];
                Array.Fill(continuities, UInt32.MaxValue);
            }

            bool result = processors[pid] == null;
            processors[pid] = packageProcessor;
            return result;
        }

        public bool IsPidProcessorPresent(int pid)
        {
            if (processors == null)
                return false;

            return processors[pid] != null;
        }
        
        public event TsPacketLossHandler OnPacketLoss;

        public int[] GetOccupiedPids()
        {
            int numListeners = 0;
            for (int i = 0; i < processors.Length; i++)
            {
                if (processors[i] != null)
                    numListeners++;
            }

            int[] result = new int[numListeners];
            int ptr = 0;
            for (int i = 0; i < processors.Length; i++)
            {
                if (processors[i] != null)
                    result[ptr++] = i;
            }

            return result;
        }

        public int CountPidsWithTraffic()
        {
	        int result = 0;
	        for (int i = 0; i < this.pidPackets.Length; i++)
	        {
		        if (this.pidPackets[i] > 0)
			        result++;
	        }

	        return result;
        }

        public ulong[] GetPidStatistics()
        {
            if (pidPackets == null)
                return null;

            ulong[] clone = (ulong[])pidPackets.Clone();
            return clone;
        }

        public ulong GetNumberOfPacketsOnPid(int pid)
        {
            return pidPackets[pid];
        }

        public ulong GetScrambledPacketsOnPid(int pid)
        {
	        if (scrambledPackets == null)
		        return 0;
	        else
		        return scrambledPackets[pid];
        }

        public ulong GetTeiPacketsOnPid(int pid)
        {
	        if (teiPackets == null)
		        return 0;
	        else
	        {
		        return teiPackets[pid];
	        }
        }

        #region TCP Proxy

        private bool _tcpProxyEnabled;
        private TcpTsProxy tcpTsProxy;
        

        public bool TcpProxyEnabled
        {
            get => _tcpProxyEnabled;
            set
            {
                if (value)
                {
                    tcpTsProxy = new TcpTsProxy();
                }
                else
                {
                    if (tcpTsProxy != null)
                        tcpTsProxy.Dispose();
                    tcpTsProxy = null;
                }
                _tcpProxyEnabled = value;
            }
        }

        public IPEndPoint GetTcpProxyEndPoint()
        {
            if (!_tcpProxyEnabled)
                return null;
            if (tcpTsProxy == null)
                return null;
            return tcpTsProxy.GetEndPoint();
        }

        #endregion

        #region PCR Monitor
        public PcrMonitor PcrMonitor { get; private set; }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void InjectIntoPcrMonitor(TsPacket tspacket)
        {
            if (tspacket.AdaptionField == null)
                return;
            if (!tspacket.AdaptionField.Valid)
                return;
            if (!tspacket.AdaptionField.PcrPresent)
                return;

            if (PcrMonitor != null)
                PcrMonitor.Analyze(tspacket);
            else
                PcrMonitor = new PcrMonitor(tspacket);
        }
        #endregion

    }

    public delegate void TsPacketLossHandler(uint pid, uint oldContinuity, uint newContinuity, long packetLossEvents);

    
}
