using log4net;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Psi.Model;
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;

namespace skyscraper5.Skyscraper.Scraper.FrameGrabber
{
    // ffmpeg -i tcp://127.0.0.1:6969 -map 0:p:10352:v:0 -vframes 1 test.png

    internal class ffmpegFrameGrabber : ITsPacketProcessor
    {
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		public int CurrentNetworkId { get; }
        public int TransportStreamId { get; }
        public ProgramMapping Mapping { get; }
        public ProgramMappingStream MappingStream { get; }
        public IFrameGrabberEventHandler EventHandler { get; }
        public IPEndPoint TsProxyEndPoint { get; set; }
        public ffmpegFrameGrabber(int currentNetworkId, int transportStreamId, ProgramMapping mapping, ProgramMappingStream mappingStream, IFrameGrabberEventHandler eventHandler)
        {
            CurrentNetworkId = currentNetworkId;
            TransportStreamId = transportStreamId;
            Mapping = mapping;
            MappingStream = mappingStream;
            EventHandler = eventHandler;
            State = FrameGrabberJobState.WAITING;

            if (eventHandler.TestForFrame(currentNetworkId, transportStreamId, mapping.ProgramNumber, mappingStream.ElementaryPid))
            {
                State = FrameGrabberJobState.DONE;
                return;
            }

        }

        private void FrameGrabberThread()
        {
	        if (!CanStart())
	        {
		        BusyFrameGrabbers--;
		        return;
	        }

	        string outputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".jpg");

            IPEndPoint endpoint = TsProxyEndPoint;
            if (TsProxyEndPoint.Address.Equals(IPAddress.Any))
            {
                endpoint = new IPEndPoint(IPAddress.Loopback, endpoint.Port);
            }

            Process ffmpeg = new Process();
            ffmpeg.StartInfo.FileName = "ffmpeg";
            ffmpeg.StartInfo.UseShellExecute = true;
            ffmpeg.StartInfo.RedirectStandardOutput = false;
            ffmpeg.StartInfo.RedirectStandardError = false;
            ffmpeg.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
            ffmpeg.StartInfo.Arguments = String.Format("-i tcp://{0} -map 0:p:{1}:v:0 -vframes 1 -vf yadif -qmin 1 -qscale:v 1 \"{2}\"", endpoint.ToString(), Mapping.ProgramNumber, outputPath);
            ffmpeg.Start();
            ffmpeg.WaitForExit();

            if (File.Exists(outputPath))
            {
                ImageData = File.ReadAllBytes(outputPath);
                File.Delete(outputPath);
                State = FrameGrabberJobState.SUCCESS;
                BusyFrameGrabbers--;
            }
            else
            {
                State = FrameGrabberJobState.FAIL;
				BusyFrameGrabbers--;
			}
        }

        public byte[] ImageData { get; set; }

        private Thread workerThread;

        public FrameGrabberJobState State { get; private set; }

        public enum FrameGrabberJobState
        {
            WAITING,
            BUSY,
            DONE,
            SUCCESS,
            FAIL
        }
        
        public void PushPacket(TsPacket packet)
        {
            if (State == FrameGrabberJobState.DONE)
                return;
            if (TsProxyEndPoint == null)
                return;
            if (startable.HasValue && !startable.Value)
                return;
            if (workerThread != null)
            {
                switch (State)
                {
                    case FrameGrabberJobState.BUSY: 
                        return;
                    case FrameGrabberJobState.FAIL:
                        State = FrameGrabberJobState.DONE;
                        return;
                    case FrameGrabberJobState.SUCCESS:
                        State = FrameGrabberJobState.DONE;
                        EventHandler.NotifyCompletedFrame(CurrentNetworkId, TransportStreamId, Mapping.ProgramNumber, MappingStream.ElementaryPid, ImageData);
                        return;
                    case FrameGrabberJobState.DONE:
                        return;
                    default:
                        throw new NotImplementedException();
                }
            }
            else
            {
                workerThread = new Thread(FrameGrabberThread);
                workerThread.Priority = ThreadPriority.Lowest;
                workerThread.Start();
                State = FrameGrabberJobState.BUSY;
                BusyFrameGrabbers++;
            }
        }

        public static int BusyFrameGrabbers { get; private set; }
        private static bool? startable;
        public static bool CanStart()
        {
            if (startable.HasValue)
                return startable.Value;

            logger.Info("Testing for a working ffmpeg...");
            Process ffmpeg = new Process();
            ffmpeg.StartInfo.FileName = "ffmpeg";
            ffmpeg.StartInfo.UseShellExecute = false;
            ffmpeg.StartInfo.RedirectStandardOutput = true;
            ffmpeg.StartInfo.RedirectStandardError = true;
            try
            {
                ffmpeg.Start();
                ffmpeg.WaitForExit();
            }
            catch (Exception e)
            {
                logger.Warn("ffmpeg doesn't work.", e);
                startable = false;
                return false;
            }
            
            string line = ffmpeg.StandardError.ReadLine();
            if (line.StartsWith("ffmpeg version 2") || line.StartsWith("ffmpeg version 5."))
            {
                logger.Info(String.Format("Found {0}", line));
                startable = true;
                return true;
            }
            int exitCode = ffmpeg.ExitCode;
            logger.DebugFormat("ffmpeg exit code {0}", exitCode);
            startable = false;

            return false;
        }
    }
}
