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

namespace skyscraper5.Mpeg2
{
    public class TsContext : ITsPacketProcessor
    {
        private ITsPacketProcessor[] processors;
        private uint[] continuities;
        private ulong[] pidPackets;

        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 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.");

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

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

			if (processors == null)
                return;

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

			bool continuity = EnsureContinuity(packet);
            processors[packet.PID].PushPacket(packet);
        }

        private bool EnsureContinuity(TsPacket packet)
        {
            //Found in ISO 13818-1.pdf, page 38
            if (packet.AdaptionFieldControl == 2 || packet.AdaptionFieldControl == 0)
                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 ulong[] GetPidStatistics()
        {
            if (pidPackets == null)
                return null;

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

        public ulong GetNumberOfPacketsOnPid(int pid)
        {
            return pidPackets[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.Adaption == null)
                return;
            if (!tspacket.Adaption.Valid)
                return;
            if (!tspacket.Adaption.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);

    
}
