using log4net;
using skyscraper5.Skyscraper.IO;
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.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Skyscraper.FrequencyListGenerator
{
	public class SatIpWithStreamReaderProxy : IStreamReader
	{
		public SatIpWithStreamReaderProxy(IStreamReader streamReader, ISsdpCache cache = null)
		{
			this.ProxiedStreamReader = streamReader;
			this.SsdpDescriptorCache = cache;
			streamReader.StopDVB();
		}

		private int satIpLof1;
		private bool satIpS2Mode;
		private int satipFrequency;
		private int satipSymbolrate;
		private DiSEqC_Opcode satIpDiseqc;
		private IPAddress satIpServerAddress;

		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
		public ISsdpCache SsdpDescriptorCache { get; private set; }
		public IStreamReader ProxiedStreamReader { get; private set; }
		public void Dispose()
		{
			throw new NotImplementedException();
		}

		public bool CheckForDVB()
		{
			throw new NotImplementedException();
		}

		public bool CheckForDVBEx(DvbEnumCallback func)
		{
			throw new NotImplementedException();
		}

		public bool CheckForDVBExEx(DvbEnumCallbackEx func)
		{
			throw new NotImplementedException();
		}

		public bool StartDVB()
		{
			throw new NotImplementedException();
		}

		public bool StartDvbEx(int index)
		{
			if (satIpServerAddress == null)
			{
				logger.Info("Looking for SAT>IP Server...");
				SsdpDevice satIpServer = SsdpClient.GetFirstSatIpServer(1000, SsdpDescriptorCache);
				if (satIpServer == null)
				{
					logger.Error("Couldn't find any SAT>IP servers.");
					return false;
				}

				satIpServerAddress = satIpServer.GetIpAddress();
				if (satIpServerAddress == null)
				{
					logger.ErrorFormat("Failed to find the IP Address of {0}", satIpServer.Server);
					return false;
				}
				logger.InfoFormat("Found SAT>IP Server at {0}", satIpServerAddress);
			}

			return ProxiedStreamReader.StartDvbEx(index);
		}

		public bool StopDVB()
		{
			return ProxiedStreamReader.StopDVB();
		}

		public bool GetTunerType(ref STD_TYPE type)
		{
			throw new NotImplementedException();
		}

		public bool SendDiSEqC(uint diseqcType, DiSEqC_Opcode data)
		{
			bool result = ProxiedStreamReader.SendDiSEqC(diseqcType, data);
			if (result)
				satIpDiseqc = data;
			return result;
		}

		public bool SendDiseqCmd(byte[] buffer, int length)
		{
			throw new NotImplementedException();
		}

		public bool SendDiseqCmdEx(nint pCmd, int length, nint reply, int replyLength)
		{
			throw new NotImplementedException();
		}

		public bool SetChannel(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw)
		{
			satipFrequency = freq / 1000;
			satipSymbolrate = symbrate / 1000;
			satIpLof1 = lof1;
			return ProxiedStreamReader.SetChannel(freq, symbrate, pol, fec, lof1, lof2, lofsw);
		}

		public bool SetChannelEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod)
		{
			throw new NotImplementedException();
		}

		public bool SetChannelExEx(int freq, int symbrate, int pol, VITERBIRATE_TYPE fec, int lof1, int lof2, int lofsw, MOD_TYPE mod,
			int inv, int pilot, ROLLOFF_TYPE rolloff)
		{
			throw new NotImplementedException();
		}

		private bool rtspStopped;
		private bool rtspStopAsked;
		private Thread rtspWorkerThread;
		private IntPtr rtspDummyHandle;
		private StdcallDvbCallback rtspCallback;
		private RtspSetupResponse rtspSetup;
		private Queue<byte[]> rtspPacketQueue;
		private string rtspUrl;
		private RtspClient rtspClient;
		public bool SetFilter(int pid, StdcallDvbCallback func, int callbackType, int size, ref nint lpFilterNum)
		{
			rtspClient = new RtspClient(satIpServerAddress, 554);
			RtspKeepalive();
			rtspUrl = RtspClient.MakeUrl(satIpDiseqc, satipFrequency, satIpS2Mode, satipSymbolrate, satIpMisEnabled ? satIpMisFilter : null, false);
			RtspDescribeResponse describe = rtspClient.GetDescribe(rtspUrl);
			if (describe.RtspStatusCode != 200)
			{
				return false;
			}
			SessionDescriptionProtocol sessionDescriptionProtocol = describe.GetSessionDescriptionProtocol();
			
			rtspPacketQueue = new Queue<byte[]>();
			rtspSetup = rtspClient.GetSetup(rtspUrl);
			if (rtspSetup.RtspStatusCode != 200)
			{
				return false;
			}
			rtspSetup.OnRtcpPacket += RtspSetup_OnRtcpPacket;
			rtspSetup.OnRtpPacket += RtspSetup_OnRtpPacket;
			rtspCallback = func;

			RtspPlayResponse play = rtspClient.GetPlay(rtspSetup);
			if (play.RtspStatusCode != 200)
			{
				return false;
			}

			rtspDummyHandle = new nint(9001);
			lpFilterNum = rtspDummyHandle;
			rtspStopAsked = false;
			rtspWorkerThread = new Thread(RtspWorkerThreadFunction);
			rtspWorkerThread.Name = "RTSP Worker Thread";
			rtspWorkerThread.Start();
			return true;
		}

		private void RtspWorkerThreadFunction()
		{
			IntPtr unsafeBuffer = Marshal.AllocHGlobal(188);

			DateTime lastKnownTimestamp = DateTime.Now;
			while (!rtspStopAsked)
			{
				DateTime currentTimestamp = DateTime.Now;
				if (currentTimestamp.Second != lastKnownTimestamp.Second)
				{
					RtspKeepalive(rtspUrl);
					lastKnownTimestamp = currentTimestamp;
				}

				if (rtspPacketQueue.Count > 0)
				{
					byte[] dequeue = rtspPacketQueue.Dequeue();
					if (dequeue == null)
						continue;
					for (int i = 12; i < dequeue.Length; i += 1)
					{
						if (dequeue[i] == 'G')
						{
							Marshal.Copy(dequeue, i, unsafeBuffer, 188);
							i += 187;
							rtspCallback(unsafeBuffer, 188);
						}
						else
						{
							throw new Exception("Help! I lost my sync byte!");
						}
					}
				}
				else
				{
					Thread.Sleep(1);
				}
			}

			RtspTeardownResponse teardown = rtspClient.GetTeardown(rtspSetup);
			rtspStopAsked = false;
			rtspStopped = true;
			Marshal.FreeHGlobal(unsafeBuffer);
		}

		private void RtspSetup_OnRtpPacket(byte[] data, int length)
		{
			byte[] queuableBuffer = new byte[length];
			Array.Copy(data, 0, queuableBuffer, 0, length);
			rtspPacketQueue.Enqueue(queuableBuffer);
		}

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

		private void RtspKeepalive(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));
			}
		}

		public bool SetFilterEx(int pid, StdcallDvbCallbackEx lpFunc, int callbackType, int size, ref nint lpFilterNum)
		{
			throw new NotImplementedException();
		}

		public bool SetBitFilter(int pid, nint filterData, nint filterMask, byte filterLength, StdcallDvbCallback lpFunc,
			ref nint lpFilterNum)
		{
			throw new NotImplementedException();
		}

		public bool SetRemoteControl(int irtype, short devAddr, StdcallDvbCallback func, ref nint lpFilterNum)
		{
			throw new NotImplementedException();
		}

		public bool DelFilter(nint filterNum)
		{
			rtspStopAsked = true;
			while (!rtspStopped)
				Thread.Sleep(10);

			rtspStopped = false;
			rtspStopAsked = false;
			rtspWorkerThread = null;
			rtspDummyHandle = IntPtr.Zero;
			rtspCallback = null;
			rtspSetup = null;
			rtspPacketQueue = null;
			rtspUrl = null;
			rtspClient = null;

			return true;
		}

		public bool GetSignal(ref int pStrength, ref int pQuality)
		{
			throw new NotImplementedException();
		}

		public bool GetSignalStrength(ref float pStrength, ref float pQuality)
		{
			throw new NotImplementedException();
		}

		public bool GetSignalEx(ref float pSNR, ref float pBER)
		{
			throw new NotImplementedException();
		}

		public bool GetSignalExEx(ref bool pPresent, ref bool pLock, ref int pRFLevel, ref float pSNR, ref float pBER)
		{
			throw new NotImplementedException();
		}

		public bool Statistic(int[] pStat)
		{
			throw new NotImplementedException();
		}

		public bool GetMAC(byte[] pMac)
		{
			throw new NotImplementedException();
		}

		public Caps GetCaps()
		{
			throw new NotImplementedException();
		}

		public bool RFScan(int freq, int pol, int lof1, int lof2, int lofsw, out double pRFLevel)
		{
			return ProxiedStreamReader.RFScan(freq, pol, lof1, lof2, lofsw, out pRFLevel);
		}

		public bool FFTInit()
		{
			throw new NotImplementedException();
		}

        public bool FFTScan(int freq, int pol, int lof1, int lof2, int lofsw, uint range, byte mode, byte nb_acc, uint[] pTab, ref uint pBegin, ref uint pNum)
        {
			throw new NotImplementedException();
		}

		public bool BLScan(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr,
			ref SearchResult pSearchResult)
		{
			throw new NotImplementedException();
		}

		public bool BLScanEx(int freq, int freq_range, int pol, int lof1, int lof2, int lofsw, int minsr, STD_TYPE std,
			ref SearchResult pSearchResult)
		{
			satIpMisEnabled = false;
			bool result = ProxiedStreamReader.BLScanEx(freq, freq_range, pol, lof1, lof2, lofsw, minsr, std, ref pSearchResult);
			if (result)
			{
				satipFrequency = pSearchResult.Freq / 1000;
				satipSymbolrate = pSearchResult.SR / 1000;
				switch (pSearchResult.StdType)
				{
					case 0:
						//For some reason, StreamReader says this when there's no lock?
						satIpS2Mode = false;
						break;
					case 1:
						satIpS2Mode = false;
						break;
					case 2:
						satIpS2Mode = true;
						break;
					default:
						throw new NotImplementedException(String.Format("{0} {1}", nameof(pSearchResult.StdType), pSearchResult.StdType));
				}
			}

			return result;
		}

		public bool BLScan2(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofsw, nint pSearchResult, ref int pTpNum,
			BlScanCallback lpFunc)
		{
			return ProxiedStreamReader.BLScan2(freq_start, freq_stop, pol, lof1, lof2, lofsw, pSearchResult, ref pTpNum, lpFunc);
		}

		public bool SignalInfo(ref SearchResult pSearchResult)
		{
			bool result = ProxiedStreamReader.SignalInfo(ref pSearchResult);
			if (result)
			{
				switch (pSearchResult.StdType)
				{
					case 0:
						//For some reason, StreamReader says this when there's no lock?
						satIpS2Mode = false;
						break;
					case 1:
						satIpS2Mode = false;
						break;
					case 2:
						satIpS2Mode = true;
						break;
					default:
						throw new NotImplementedException(String.Format("{0} {1}", nameof(pSearchResult.StdType), pSearchResult.StdType));
				}
			}
			return result;
		}

		public bool IQScan(uint input, sbyte[] pIQ, uint num)
		{
			return ProxiedStreamReader.IQScan(input, pIQ, num);
		}

		public bool IQScan2(uint point, short[] pIQ, uint num)
		{
			throw new NotImplementedException();
		}

		public bool IQScan2Range(byte input, ref ushort pMinPoint, ref ushort pMaxPoint)
		{
			throw new NotImplementedException();
		}

		public bool CIRScanRange(bool bHiRes, ref ushort pMinCnum, ref ushort pMaxCnum, ref int pMinDelayNs, ref int pMaxDelayNs)
		{
			throw new NotImplementedException();
		}

		public bool CIRScan(bool bHiRes, int[] pPowers, int[] pDelays)
		{
			throw new NotImplementedException();
		}

		public bool CarRange(ref ushort pMinCnum, ref ushort pMaxCnum)
		{
			throw new NotImplementedException();
		}

		public bool CarEsNo(ref ushort cnum, ref double pEsNo)
		{
			throw new NotImplementedException();
		}

		private bool satIpMisEnabled;
		private byte satIpMisFilter;
		private byte satIpMisFilterMask;
		public bool MISSel(bool bEnable, byte misFilter, byte misFilterMask)
		{
			this.satIpMisEnabled = bEnable;
			this.satIpMisFilter = misFilter;
			this.satIpMisFilterMask = misFilterMask;
			return ProxiedStreamReader.MISSel(bEnable, misFilter, misFilterMask);
		}

		public bool PLSSel(byte plsMode, uint code)
		{
			throw new NotImplementedException();
		}

		public bool PLSGet(byte pMode, ref uint pCode)
		{
			throw new NotImplementedException();
		}

		public bool ModSel(ref S2Mode ps2Modes, uint num)
		{
			throw new NotImplementedException();
		}

		public bool ModInv(uint WaitMs, ref S2Mode pS2Modes, ref uint pNum)
		{
			throw new NotImplementedException();
		}

		public bool SetChannel2(uint freq, uint bandwidth)
		{
			throw new NotImplementedException();
		}

		public bool SetChannel2Ex(uint freq, uint bandwidth, STD_TYPE std, int stream)
		{
			throw new NotImplementedException();
		}

		public bool SetChannel2ExEx(uint freq, uint bandwidth, uint symbrate, STD_TYPE std, int stream)
		{
			throw new NotImplementedException();
		}

		public bool SignalInfo2(ref SearchResult2 si2)
		{
			throw new NotImplementedException();
		}

		public bool RFScan2(uint freq, STD_TYPE std, ref double pRFLevel)
		{
			throw new NotImplementedException();
		}

		public bool AirScan(int freq_start, int freq_stop, uint step, uint bandwidth, int std, nint pSearchResult, ref int pTpNum,
			AirScanCallback lpFunc)
		{
			throw new NotImplementedException();
		}

		public bool GetEEPROM(byte[] buffer, int offset, int len)
		{
			throw new NotImplementedException();
		}

		public bool SetEEPROM(byte[] buffer, int offset, int len)
		{
			throw new NotImplementedException();
		}

		public Version GetEngineVersion()
		{
			return ProxiedStreamReader.GetEngineVersion();
		}

		public string GetEngineName()
		{
			return ProxiedStreamReader.GetEngineName();
		}

		public bool FFTTerm()
		{
			return ProxiedStreamReader.FFTTerm();
		}
	}
}
