using log4net;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper8.SatIp;
using skyscraper8.SatIp.RtspResponses;
using skyscraper8.SimpleServiceDiscoveryProtocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.Scraper;
using skyscraper5.Skyscraper.Scraper.Storage.Filesystem;
using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
using skyscraper8.Skyscraper.Scraper.Storage;

namespace skyscraper8
{
	internal class QuickAndDirtySatIpClient
	{
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
		private const bool ENABLE_TS_WRITER = true;

		public QuickAndDirtySatIpClient(string[] args)
		{
			if (args.Length == 1)
			{
				logger.Fatal("Hey, what's your SAT>IP Server's IP Address? You can also say \"autodetect\" here.");
				return;
			}
			if (args[1].ToLowerInvariant().Contains("auto"))
			{
				ipAddress = AutodetectIPAddress();
			}
			else
			{
				ipAddress = IPAddress.Parse(args[1]);
			}

			if (args.Length == 2)
			{
				logger.Fatal("You didn't specify a DiSEqc setting. (Number between 1-4)");
				return;
			}
			diseqcNumber = Int32.Parse(args[2]);

			if (args.Length == 3)
			{
				logger.Fatal("You didn't specify a polarity. Use H or V.");
				return;
			}
			polarity = args[3].ToUpperInvariant()[0];

			if (args.Length == 4)
			{
				logger.Fatal("Please specify a frequency.");
				return;
			}
			frequency = Int32.Parse(args[4]);

			if (args.Length == 5)
			{
				logger.Fatal("What DVB Standard do we have here? S or S2?");
				return;
			}
			isS2 = ParseDvbStandard(args[5]);

			if (args.Length == 6)
			{
				logger.Fatal("What's the symbol rate?");
				return;
			}
			symbolRate = Int32.Parse(args[6]);
		}

		public void SetPlayoutMode(string[] args)
		{
			if (args.Length == 7)
			{
				logger.Fatal("What's the target port?");
				return;
			}
			destinationPort = int.Parse(args[7]);
			playoutMode = true;
			if (args.Length == 10)
			{
				if (args[8].ToLowerInvariant().Equals("multicast"))
				{
					destinationIp = IPAddress.Parse(args[9]);
					multicastMode = true;
				}
				else
				{
					logger.FatalFormat("Don't know what {0} means.", args[8]);
					return;
				}
			}
		}

		private IPAddress AutodetectIPAddress()
		{
			SsdpDevice firstSatIpServer = SsdpClient.GetFirstSatIpServer(1000, null);
			if (firstSatIpServer == null)
			{
				logger.WarnFormat("Didn't find any SAT>IP servers.");
				return null;
			}
			IPAddress ipAddress = firstSatIpServer.GetIpAddress();
			logger.InfoFormat("Found SAT>IP Server at {0}", ipAddress);
			return ipAddress;
		}

		private int symbolRate;
		private bool isS2;
		private int frequency;
		private char polarity;
		private int diseqcNumber;
		private IPAddress ipAddress;
		private RtspClient rtspClient;
		public void Run()
		{
			rtspClient = new RtspClient(ipAddress, 554);
			rtspClient.AutoReconnect = false;
			Keepalive();
			DiSEqC_Opcode opcode = BuildDiseqcOpcode();
			string url = RtspClient.MakeUrl(opcode, frequency, isS2, symbolRate, null, false);
			RtspDescribeResponse describe = rtspClient.GetDescribe(url);
			SessionDescriptionProtocol sessionDescriptionProtocol = describe.GetSessionDescriptionProtocol();

			packetQueue = new Queue<byte[]>();
			RtspSetupResponse setup = rtspClient.GetSetup(url, destinationPort, multicastMode, destinationIp);
			if (setup.RtspStatusCode == 404)
			{
				logger.Fatal("Your SAT>IP server doesn't have a tuner available that can talk to the requested frequency.");
				return;
			}
			setup.OnRtcpPacket += Setup_OnRtcpPacket;
			setup.OnRtpPacket += Setup_OnRtpPacket;

			if (dataStorage == null)
				dataStorage = new InMemoryScraperStorage();
			if (objectStorage == null)
				objectStorage = new FilesystemStorage(new DirectoryInfo("."));
			if (!playoutMode)
			{
				context = new SkyscraperContext(new TsContext(), dataStorage, objectStorage);
				context.EnableTimeout = true;
				context.TimeoutSeconds = 60;
				context.InitalizeFilterChain();
			}

			RtspPlayResponse play = rtspClient.GetPlay(setup);
			DateTime lastTimestamp = DateTime.Now;
			bool initMessagePrinted = false;

			while (true)
			{
				if (playoutMode)
				{
					Thread.Sleep(1000);
					Keepalive(url);
					if (!initMessagePrinted)
					{
						logger.InfoFormat("Began SAT>IP playout to port {0}", destinationPort);
						initMessagePrinted = true;
					}
				}
				else
				{
					if (packetQueue.Count >= 1)
					{
						byte[] buffer;
						lock (packetQueue)
						{
							buffer = packetQueue.Dequeue();
						}
						context.IngestSinglePacket(buffer);
						if (!initMessagePrinted)
						{
							logger.InfoFormat("Began SAT>IP stream.");
							initMessagePrinted = true;
						}
					}
					else
					{
						Thread.Sleep(1);
					}

					if (context.IsAbortConditionMet())
						break;

					if (DateTime.Now.Second != lastTimestamp.Second)
					{
						Keepalive(url);
						lastTimestamp = DateTime.Now;
					}
				}
			}

			rtspClient.GetTeardown(setup);
			rtspClient.Dispose();
		}

		private FileStream fs;
		private uint stuffingBytes;
		private Queue<byte[]> packetQueue;
		private ObjectStorage objectStorage;
		private DataStorage dataStorage;
		private SkyscraperContext context;
		private void Setup_OnRtpPacket(byte[] data, int length)
		{
			for (int i = 12; i < length; i += 1)
			{
				if (data[i] == 'G')
				{
					byte[] buffer = new byte[188];
					Array.Copy(data, i, buffer, 0, 188);
					lock (packetQueue)
					{
						packetQueue.Enqueue(buffer);
					}

					DumpPacket(buffer);
					i += 187;
				}
				else if (data[i] == 0xff)
				{
					stuffingBytes++;
				}
			}
		}

		private void DumpPacket(byte[] buffer)
		{
			if (!ENABLE_TS_WRITER)
				return;

			if (fs == null)
			{
				string fname = String.Format("{0}.ts", DateTime.Now.Ticks);
				fs = File.OpenWrite(fname);
			}

			fs.Write(buffer, 0, buffer.Length);
		}

		private int rtcps;
        private IPAddress destinationIp;
        private int destinationPort;
        private bool playoutMode;
		private bool multicastMode;

        private void Setup_OnRtcpPacket(byte[] data, int length)
		{
			//rtcps++;
		}

		private DiSEqC_Opcode BuildDiseqcOpcode()
		{
			DiSEqC_Opcode opcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE;
			switch (diseqcNumber)
			{
				case 1:
					opcode |= DiSEqC_Opcode.DISEQC_OPTION_A;
					opcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
					break;
				case 2:
					opcode |= DiSEqC_Opcode.DISEQC_OPTION_A;
					opcode |= DiSEqC_Opcode.DISEQC_POSITION_B;
					break;
				case 3:
					opcode |= DiSEqC_Opcode.DISEQC_OPTION_B;
					opcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
					break;
				case 4:
					opcode |= DiSEqC_Opcode.DISEQC_OPTION_B;
					opcode |= DiSEqC_Opcode.DISEQC_POSITION_B;
					break;
				default:
					throw new ArgumentOutOfRangeException("Your DiSEqC position should be a number between 1 and 4.");
			}

			switch (polarity)
			{
				case 'H':
					opcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL;
					break;
				case 'V':
					opcode |= DiSEqC_Opcode.DISEQC_VERTICAL;
					break;
				default:
					throw new ArgumentException("The polarity should be H or V.");
			}

			return opcode;
		}

		private bool ParseDvbStandard(string standard)
		{
			standard = standard.ToUpperInvariant();
			if (standard.StartsWith("DVB"))
				standard = standard.Substring(3);
			if (standard.StartsWith("-"))
				standard = standard.Substring(1);

			switch (standard)
			{
				case "S":
					return false;
				case "S2":
					return true;
				case "S2X":
					return true;
				default:
					throw new ArgumentException(String.Format("I have no idea what kind of Standard {0} is supposed to be.", standard));
			}
		}

		private void Keepalive(string url = "/")
		{
			RtspOptionsResponse options = rtspClient.GetOptions("/");
			if (options.RtspStatusCode != 200)
			{
				throw new RtspException(String.Format("Unexpected RTSP Status code. Wanted {0}, got {1}", 200, options.RtspStatusCode));
			}
		}
	}
}
