using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Psi;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.Skyscraper.Scraper.StreamAutodetection.Contestants;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper5.Skyscraper.Scraper.StreamAutodetection
{
    class StreamTypeAutodetection : ITsPacketProcessor, IDisposable, IPayloadUnitDecoder
    {
        public int Pid { get; }
        public IAutodetectionEventHandler EventHandler { get; }

        public ProgramContext ProgramContext { get; }

        private const bool CATCH_EXCEPTIONS = true;

        public StreamTypeAutodetection(int pid, IAutodetectionEventHandler eventHandler)
        {
            Pid = pid;
            EventHandler = eventHandler;
            Scoreboard = CreateScoreboard(pid);
            ProgramContext = new ProgramContext();
        }

        private Contestant[] Scoreboard;
        private bool winnerDetermined;
        private List<Exception> memorials;

        public void PushPacket(TsPacket packet)
        {
            if (winnerDetermined)
                throw new ApplicationException("Autodetection contest winner declared, but still pushing packets.");

            for (int i = 0; i < Scoreboard.Length; i++)
            {
                if (CATCH_EXCEPTIONS)
                {
                    try
                    {
                        if (Scoreboard[i] != null)
                            Scoreboard[i].PacketProcessor.PushPacket(packet);
                    }
                    catch (Exception e)
                    {
                        if (memorials == null)
                            memorials = new List<Exception>();
                        memorials.Add(e);
                        Scoreboard[i].Score--;
                    }
                    if (Scoreboard[i].Score <= -10)
                    {
                        //Console.WriteLine("PID 0x{0:X4} is probably not for {1}", Pid, Scoreboard[i].GetType().Name);
                        EventHandler.AutodetectionRuleOut(Pid, Scoreboard[i], ProgramContext);
                        Scoreboard[i] = new DisqualifiedContestant(Scoreboard[i].Pid);
                    }
                }
                else
                {
                    if (Scoreboard[i] != null)
                        Scoreboard[i].PacketProcessor.PushPacket(packet);
                }
            }

            CheckWinner();
        }

        private bool CheckWinner()
        {
	        bool result = false;
	        if (SomethingHappened())
	        {
		        Array.Sort(Scoreboard, (x, y) => y.Score.CompareTo(x.Score));
		        Contestant leader = Scoreboard[0];
		        Contestant runnerUp = Scoreboard[1];
		        long scoreDifference = leader.Score - runnerUp.Score;
		        if (scoreDifference > 5)
		        {
			        EventHandler.AutodetectionSucessful(Pid, leader, ProgramContext);
			        winnerDetermined = true;
			        Dispose();
			        result = true;
		        }
	        }

	        return result;
        }

        private bool SomethingHappened()
        {
            foreach (Contestant contestant in Scoreboard)
                if (contestant.Score != 0)
                    return true;
            return false;
        }

        #region Scoreboard Creation
        public static Contestant[] CreateScoreboard(int pid)
        {
	        ReadOnlyCollection<Type> contestants = PluginManager.GetInstance().GetStreamTypeAutodetectionContestants();

	        if (contestants.Count == 0)
		        throw new ApplicationException("failed to load autodetection modules");

	        if (contestants.Count < 2)
		        throw new ApplicationException("Need at least two autodetection modules");

			Type[] intType = new Type[] {typeof(int)};
            object[] intArgs = new object[] { pid };
            Contestant[] result = new Contestant[contestants.Count];
            for (int i = 0; i < result.Length; i++)
            {
                Type spawner = contestants[i];
                ConstructorInfo constructorInfo = spawner.GetConstructor(intType);
                result[i] = (Contestant)constructorInfo.Invoke(intArgs);
            }

            return result;
        }
        #endregion

        public void Dispose()
        {
            for (int i = 0; i < Scoreboard.Length; i++)
            {
                Scoreboard[i].Dispose();
                Scoreboard[i] = null;
            }
        }

        public void PacketLoss()
        {
            if (Scoreboard == null)
                return;

            for (int i = 0; i < Scoreboard.Length; i++)
            {
                IPayloadUnitDecoder payloadUnitDecoder = Scoreboard[i].PacketProcessor as IPayloadUnitDecoder;
                if (payloadUnitDecoder != null)
                    payloadUnitDecoder.PacketLoss();
            }
        }

        public void IntroduceStreamToContestants()
        {
	        foreach (Contestant contestant in Scoreboard)
	        {
		        contestant.Introduce(ProgramContext);
	        }
            bool hasWinners = CheckWinner();

            if (!hasWinners)
            {
                //No winners? How about losers?
	            for (int i = 0; i < Scoreboard.Length; i++)
	            {
		            if (Scoreboard[i].Score <= -10)
		            {
			            //Console.WriteLine("PID 0x{0:X4} is probably not for {1}", Pid, Scoreboard[i].GetType().Name);
			            //EventHandler.AutodetectionRuleOut(Pid, Scoreboard[i], ProgramContext);
			            Scoreboard[i] = new DisqualifiedContestant(Scoreboard[i].Pid);
		            }
	            }
            }
        }

        public bool IsWinnerDetermined
        {
	        get
	        {
		        return winnerDetermined;
	        }
        }
    }
}
