using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.src.Skyscraper.FrequencyListGenerator;
using skyscraper8.Skyscraper.Plugins;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper8.Skyscraper.Drawing;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.src.Mpeg2.PacketFilter;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Scraper;
using System.Runtime.InteropServices;

namespace skyscraper8.Skyscraper.FrequencyListGenerator
{
	public class BaseBlindscanJob
	{
		private static PluginLogger logger = PluginLogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		public BaseBlindscanJob(BlindscanJobConfiguration config)
		{
			if (config == null)
			{
				throw new ArgumentNullException(nameof(config));
			}

			this.config = config;
		}

		private BlindscanJobConfiguration config;
		private DbBlindscanJob jobInDb;
		private List<BlindscanSearchResult> foundFrequencies;
		
		public void Run()
		{
			if (config == null)
				throw new NullReferenceException("No config? Dude, what?");
			if (config.StreamReader == null)
				throw new NullReferenceException("No StreamReader available");


			jobInDb = new DbBlindscanJob(Guid.NewGuid(), config.TunerMetadata.MacAddress, config.TunerMetadata.Type, config.DiseqcIndex, config.Gps, config.SatellitePosition);
			jobInDb.HorizontalHighState = config.DoHorizontalHigh ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED;
			jobInDb.HorizontalLowState = config.DoHorizontalLow ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED;
			jobInDb.VerticalLowState = config.DoVerticalLow ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED;
			jobInDb.VerticalHighState = config.DoVerticalHigh ? DbBlindscanJobPolarizationStatus.SELECTED_WAITING : DbBlindscanJobPolarizationStatus.NOT_SELECTED;
			config.DataStorage.InsertBlindscanJob(jobInDb);

			foundFrequencies = new List<BlindscanSearchResult>();
			config.Ui.OnBlindscanOpenFoundFrquenciesWindow(foundFrequencies, config.TunerMetadata.Type);

			if (!config.StreamReader.StartDvbEx(config.TunerMetadata.Index))
			{
				if (!config.StreamReader.StopDVB())
				{
					throw new Exception("failed to stop DVB in order to restart it.");
				}

				if (!config.StreamReader.StartDvbEx(config.TunerMetadata.Index))
				{
					throw new Exception("Failed to start DVB");
				}
			}
			if (!RunBlindscan())
			{
				config.StreamReader.StopDVB();
				config.Ui.OnBlindscanJobDone(false);
				return;
			}

			config.Ui.OnBlindscanJobDone(true);
			logger.Log(PluginLogLevel.Info,"Blindscan Job done!");
			return;
		}

		public bool RunBlindscan()
		{
			Caps caps = config.TunerMetadata.Caps;

			if (config.DoCollectRfSpectrum)
			{
				RfSpectrumData rfSpectrum = GatherRfSpectrum();
				if (rfSpectrum != null)
				{
					config.ObjectStorage.StoreRfSpectrum(jobInDb.JobGuid, rfSpectrum);
				}
			}
			if (caps.HasFlag(Caps.SR_BLSCAN2))
			{
				SearchResult sr1 = default;
				int tpNum = default;

				int lof1 = config.Lof1 * 1000;
				int lof2 = config.Lof2 * 1000;
				int lofSw = config.LofSw * 1000;
				int diseqc = config.TunerMetadata.DiseqcType;
				int startFreq = config.MinimumFrequency * 1000;
				int endFreq = config.MaximumFrequency * 1000;
				bool anythingSuceeded = false;
				nint allocHGlobal = Marshal.AllocHGlobal(ushort.MaxValue);

				if (lofSw != 0)
				{
					if (config.DoHorizontalLow)
					{
						if (SendDiseqcCommand(false, true))
						{
							logger.Log(PluginLogLevel.Info,"Scanning low horizontal band...");
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING;
							config.DataStorage.UpdateJobState(jobInDb);
							bool hLower = BlScan2Wrap(startFreq, lofSw, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult sr1) => SearchResult1Callback(sr1, 1));
							config.Ui.OnBlindscanBandComplete();
							if (!hLower)
							{
								config.Ui.OnBlindscanErrorMessage("Blindscanning low horizontal area failed.");
								jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED;
								config.DataStorage.UpdateJobState(jobInDb);
							}
							else
							{
								anythingSuceeded = true;
								jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING;
								config.DataStorage.UpdateJobState(jobInDb);
								SendDiseqcCommand(false, true);
								RunScrape();
								jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
								config.DataStorage.UpdateJobState(jobInDb);
								foundFrequencies.Clear();
							}
						}
						else
						{
							config.Ui.OnBlindscanErrorMessage("DiSEqC Switch to low horizontal area failed.");
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC;
							config.DataStorage.UpdateJobState(jobInDb);
						}
					}

					if (config.DoHorizontalHigh)
					{
						if (SendDiseqcCommand(true, true))
						{
							logger.Log(PluginLogLevel.Info,"Scanning high horizontal band...");
							jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING;
							config.DataStorage.UpdateJobState(jobInDb);
							bool hUpper = BlScan2Wrap(lofSw, endFreq, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult sr1) => SearchResult1Callback(sr1, 2));
							config.Ui.OnBlindscanBandComplete();
							if (!hUpper)
							{
								config.Ui.OnBlindscanErrorMessage("Blindscanning high horizontal area failed.");
								jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED;
								config.DataStorage.UpdateJobState(jobInDb);
							}
							else
							{
								anythingSuceeded = true;
								jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING;
								config.DataStorage.UpdateJobState(jobInDb);
								SendDiseqcCommand(true, true);
								RunScrape();
								jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
								config.DataStorage.UpdateJobState(jobInDb);
								foundFrequencies.Clear();
							}
						}
						else
						{
							config.Ui.OnBlindscanErrorMessage("DiSEqC Switch to high horizontal area failed.");
							jobInDb.HorizontalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC;
							config.DataStorage.UpdateJobState(jobInDb);
						}
					}

					if (config.DoVerticalLow)
					{
						if (SendDiseqcCommand(false, false))
						{
							logger.Log(PluginLogLevel.Info,"Scanning low vertical band...");
							jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING;
							config.DataStorage.UpdateJobState(jobInDb);
							bool vLower = BlScan2Wrap(startFreq, lofSw, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult sr1) => SearchResult1Callback(sr1, 3));
							config.Ui.OnBlindscanBandComplete();
							if (!vLower)
							{
								config.Ui.OnBlindscanErrorMessage("Blindscanning low vertical area failed.");
								jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED;
								config.DataStorage.UpdateJobState(jobInDb);
							}
							else
							{
								anythingSuceeded = true;
								jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING;
								config.DataStorage.UpdateJobState(jobInDb);
								SendDiseqcCommand(false, false);
								RunScrape();
								jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
								config.DataStorage.UpdateJobState(jobInDb);
								foundFrequencies.Clear();
							}
						}
						else
						{
							config.Ui.OnBlindscanErrorMessage("DiSEqC Switch to low vertical area failed.");
							jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC;
							config.DataStorage.UpdateJobState(jobInDb);
						}
					}

					if (config.DoVerticalHigh)
					{
						if (SendDiseqcCommand(true, false))
						{
							logger.Log(PluginLogLevel.Info,"Scanning high vertical band...");
							jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING;
							config.DataStorage.UpdateJobState(jobInDb);
							bool vUpper = BlScan2Wrap(lofSw, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult sr1) => SearchResult1Callback(sr1, 4));
							config.Ui.OnBlindscanBandComplete();
							if (!vUpper)
							{
								config.Ui.OnBlindscanErrorMessage("Blindscanning high vertical area failed.");
								jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED;
								config.DataStorage.UpdateJobState(jobInDb);
							}
							else
							{
								anythingSuceeded = true;
								jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING;
								config.DataStorage.UpdateJobState(jobInDb);
								SendDiseqcCommand(true, false);
								RunScrape();
								jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
								config.DataStorage.UpdateJobState(jobInDb);
								foundFrequencies.Clear();
							}
						}
						else
						{
							config.Ui.OnBlindscanErrorMessage("DiSEqC Switch to high vertical area failed.");
							jobInDb.VerticalHighState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC;
							config.DataStorage.UpdateJobState(jobInDb);
						}
					}
				}
				else
				{
					if (SendDiseqcCommand(false, true))
					{
						logger.Log(PluginLogLevel.Info, string.Format("Scanning left circular band..."));
						jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING;
						config.DataStorage.UpdateJobState(jobInDb);
						bool lCirc = BlScan2Wrap(startFreq, endFreq, 0, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult sr1) => SearchResult1Callback(sr1, 1));
						config.Ui.OnBlindscanBandComplete();
						if (!lCirc)
						{
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED;
							config.DataStorage.UpdateJobState(jobInDb);
							config.Ui.OnBlindscanErrorMessage("Blindscanning left circular area failed.");
						}
						else
						{
							anythingSuceeded = true;
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING;
							config.DataStorage.UpdateJobState(jobInDb);
							SendDiseqcCommand(false, true);
							RunScrape();
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
							config.DataStorage.UpdateJobState(jobInDb);
							foundFrequencies.Clear();
						}
					}
					else
					{
						config.Ui.OnBlindscanErrorMessage("DiSEqC Switch to left circulation failed.");
						jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED_DISEQC;
						config.DataStorage.UpdateJobState(jobInDb);
					}
					if (SendDiseqcCommand(false, false))
					{
						logger.Log(PluginLogLevel.Info, string.Format("Scanning right circular band..."));
						jobInDb.VerticalLowState = DbBlindscanJobPolarizationStatus.SELECTED_BLINDSCANNING;
						config.DataStorage.UpdateJobState(jobInDb);
						bool rCirc = BlScan2Wrap(startFreq, endFreq, 1, lof1, lof2, lofSw, allocHGlobal, ref tpNum, (ref SearchResult sr1) => SearchResult1Callback(sr1, 3));
						config.Ui.OnBlindscanBandComplete();
						if (!rCirc)
						{
							config.Ui.OnBlindscanErrorMessage("Blindscanning right circular area failed.");
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_FAILED;
							config.DataStorage.UpdateJobState(jobInDb);
						}
						else
						{
							anythingSuceeded = true;
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_SCRAPING;
							config.DataStorage.UpdateJobState(jobInDb);
							SendDiseqcCommand(false, false);
							RunScrape();
							jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
							config.DataStorage.UpdateJobState(jobInDb);
							foundFrequencies.Clear();
						}
					}
					else
					{
						config.Ui.OnBlindscanErrorMessage("DiSEqC Switch to right circulation failed.");
						jobInDb.HorizontalLowState = DbBlindscanJobPolarizationStatus.SELECTED_DONE;
						config.DataStorage.UpdateJobState(jobInDb);
					}
				}

				Marshal.FreeHGlobal(allocHGlobal);
				return anythingSuceeded;
			}
			else
			{
				throw new NotImplementedException("Don't know how to blindscan with this tuner.");
			}
		}
		

		private bool BlScan2Wrap(int freq_start, int freq_stop, int pol, int lof1, int lof2, int lofSw, nint pSearchResult, ref int pTpNum, BlScanCallback lpFunc)
		{
			config.Ui.OnBlindscanBeforeBLScan(freq_start, freq_start, freq_stop);

			bool result = config.StreamReader.BLScan2(freq_start, freq_stop, pol, lof1, lof2, lofSw, pSearchResult, ref pTpNum, lpFunc);

			config.Ui.OnBlindscanAfterBLScan();

			return result;
		}

		private bool SendDiseqcCommand(bool highBand, bool horizontal)
		{
			DiSEqC_Opcode myOpcode = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE;
			if (highBand)
				myOpcode |= DiSEqC_Opcode.DISEQC_HIGH_BAND;
			else
				myOpcode |= DiSEqC_Opcode.DISEQC_LOW_BAND;

			if (horizontal)
				myOpcode |= DiSEqC_Opcode.DISEQC_HORIZONTAL;
			else
				myOpcode |= DiSEqC_Opcode.DISEQC_VERTICAL;

			int parameter = config.DiseqcIndex;
			switch (parameter)
			{
				case 1:
					myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A;
					myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
					break;
				case 2:
					myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_A;
					myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B;
					break;
				case 3:
					myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B;
					myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_A;
					break;
				case 4:
					myOpcode |= DiSEqC_Opcode.DISEQC_OPTION_B;
					myOpcode |= DiSEqC_Opcode.DISEQC_POSITION_B;
					break;
				default:
					throw new ArgumentOutOfRangeException("DiSEqC Switch Position");
			}

			logger.Log(PluginLogLevel.Info, string.Format("Send DiSEqC Command {0:X2}", (byte)myOpcode), 8);

			DateTime started = DateTime.Now;
			bool result = config.StreamReader.SendDiSEqC(2, myOpcode);
			TimeSpan timeTaken = DateTime.Now - started;
			logger.Log(PluginLogLevel.Debug, string.Format("DiSEqC Comannd sent in {0:F1} seconds.", timeTaken.TotalSeconds));
			if (timeTaken.TotalSeconds > 10)
			{
				logger.Log(PluginLogLevel.Warn, string.Format("Something went wrong while performing the DiSEqC operation, trying again..."), 8);
				Thread.Sleep(1000);
				return SendDiseqcCommand(highBand, horizontal);
			}

			return result;
		}

		private void SearchResult1Callback(SearchResult searchResult, int polarityIndex)
		{
			BlindscanSearchResult blindscanSearchResult = new BlindscanSearchResult();
			blindscanSearchResult.SearchResult = searchResult;
			blindscanSearchResult.Standard = (STD_TYPE)searchResult.StdType;
			logger.Log(PluginLogLevel.Info, string.Format("Found frequency: {0}, {1}", searchResult.Freq / 1000, searchResult.Pol == 0 ? "H" : "V"));
			lock (foundFrequencies)
			{
				foundFrequencies.Add(blindscanSearchResult);
			}
			config.DataStorage.InsertSearchResult(jobInDb, true, searchResult, polarityIndex, new SearchResult2());
			config.Ui.OnBlindscanSearchResult1Callback(blindscanSearchResult, polarityIndex, config.MinimumFrequency, config.MaximumFrequency);
		}


		public void RunScrape()
		{
			int lof1 = config.Lof1 * 1000;
			int lof2 = config.Lof2 * 1000;
			int lofSw = config.LofSw * 1000;


			foreach (BlindscanSearchResult blindscanResult in foundFrequencies)
			{
				DateTime now = DateTime.Now;

				blindscanResult.State = BlindscanResultState.Tuning;
				config.DataStorage.UpdateTransponderState(jobInDb, blindscanResult.IsSatellite(), blindscanResult.SearchResult, blindscanResult.State, blindscanResult.SearchResult2);
				config.Ui.OnBlindscanBeforeSetChannel(blindscanResult);
				bool channel = config.StreamReader.SetChannel(blindscanResult.SearchResult.Freq, blindscanResult.SearchResult.SR, blindscanResult.SearchResult.Pol, blindscanResult.SearchResult.FEC, lof1, lof2, lofSw);
				if (!channel)
				{
					blindscanResult.State = BlindscanResultState.TuningFailed;
					config.DataStorage.UpdateTransponderState(jobInDb, blindscanResult.IsSatellite(), blindscanResult.SearchResult, blindscanResult.State, blindscanResult.SearchResult2);
					continue;
				}
				
				RunSkyscraper(blindscanResult);
				config.Ui.OnBlindscanScrapeTransponderComplete(blindscanResult);
			}
			
			config.Ui.OnScrapeBandComplete();
		}



		private RfSpectrumData GatherRfSpectrum()
		{
			Caps caps = config.TunerMetadata.Caps;
			if (caps.HasFlag(Caps.SR_RFSCAN2))
			{
				return GatherRfSpectrumCable();
			}
			else if (!caps.HasFlag(Caps.SR_RFSCAN))
			{
				logger.Log(PluginLogLevel.Error, "Couldn't figure out whether to use RFScan or RFScan2!");
				return null;
			}

			config.Ui.OnBlindscanBeginRfSpectrum();

			int lof1 = config.Lof1 * 1000;
			int lof2 = config.Lof2 * 1000;
			int lofSw = config.LofSw * 1000;
			int startFreq = config.MinimumFrequency * 1000;
			int endFreq = config.MaximumFrequency * 1000;

			RfSpectrumData spectrumData = RfSpectrumData.Create();

			void RunRfScan(RfSpectrumData spectrum, int startFreq, int endFreq, SatelliteDeliverySystemDescriptor.PolarizationEnum polarization, DiSEqC_Opcode diseqcCmd, int lof1, int lof2, int lofSw)
			{
				const int STEP = 1000;
				config.StreamReader.SendDiSEqC(2, diseqcCmd);
				RfSpectrumDataBlock block = spectrumData.CreateBlock(startFreq, endFreq, STEP, polarization);
				for (int i = startFreq; i <= endFreq; i += STEP)
				{
					double rf = double.NaN;
					config.StreamReader.RFScan(i, diseqcCmd.HasFlag(DiSEqC_Opcode.DISEQC_HORIZONTAL) ? 0 : 1, lof1, lof2, lofSw, out rf);
					logger.Log(PluginLogLevel.Debug,"{0} {1}, {2}", i, polarization.ToString().Substring(0, 1), rf);
					block.Push(i, rf);
					config.Ui.OnBlindscanRfSpectrumEnqueueSample(polarization, i, rf);
				}
			}

			foreach (DiSEqC_Opcode opcode in GetIqGraphDiseqcOpcodes())
			{
				RunRfScan(spectrumData, startFreq, endFreq,
					opcode.HasFlag(DiSEqC_Opcode.DISEQC_HORIZONTAL) ? SatelliteDeliverySystemDescriptor.PolarizationEnum.HorizontalLinear : SatelliteDeliverySystemDescriptor.PolarizationEnum.VerticalLinear,
					opcode, lof1, lof2, lofSw);
			}

			config.Ui.OnBlindscanEndRfSpectrum();

			return spectrumData;
		}

		private RfSpectrumData GatherRfSpectrumCable()
		{
			throw new NotImplementedException();
		}

		private IEnumerable<DiSEqC_Opcode> GetIqGraphDiseqcOpcodes()
		{
			DiSEqC_Opcode baseDiseqc = DiSEqC_Opcode.DISEQC_HIGH_NIBBLE;

			int parameter = config.DiseqcIndex;
			switch (parameter)
			{
				case 1:
					baseDiseqc |= DiSEqC_Opcode.DISEQC_OPTION_A;
					baseDiseqc |= DiSEqC_Opcode.DISEQC_POSITION_A;
					break;
				case 2:
					baseDiseqc |= DiSEqC_Opcode.DISEQC_OPTION_A;
					baseDiseqc |= DiSEqC_Opcode.DISEQC_POSITION_B;
					break;
				case 3:
					baseDiseqc |= DiSEqC_Opcode.DISEQC_OPTION_B;
					baseDiseqc |= DiSEqC_Opcode.DISEQC_POSITION_A;
					break;
				case 4:
					baseDiseqc |= DiSEqC_Opcode.DISEQC_OPTION_B;
					baseDiseqc |= DiSEqC_Opcode.DISEQC_POSITION_B;
					break;
				default:
					throw new ArgumentOutOfRangeException("DiSEqC Switch Position");
			}

			if (config.DoHorizontalLow || config.DoHorizontalHigh)
				yield return baseDiseqc | DiSEqC_Opcode.DISEQC_HORIZONTAL | DiSEqC_Opcode.DISEQC_LOW_BAND;
			if (config.DoVerticalLow || config.DoVerticalHigh)
				yield return baseDiseqc | DiSEqC_Opcode.DISEQC_VERTICAL | DiSEqC_Opcode.DISEQC_LOW_BAND;
			yield break;
		}
		private IqChartData GatherIqGraph()
		{
			Caps caps = config.TunerMetadata.Caps;
			if (caps.HasFlag(Caps.SR_IQSCAN2))
			{
				return GatherIQGraphCable();

			}
			else if (!caps.HasFlag(Caps.SR_IQSCAN))
			{
				logger.Log(PluginLogLevel.Error, "Couldn't figure out whether to use IQScan or IQScan2!");
				return null;
			}
			
			IqChartData result = IqChartData.Create();
			config.Ui.OnBlindscanBeginIqSpectrum(result);


			sbyte[] buffer = new sbyte[400];
			DateTime started = DateTime.Now;
			while (!result.IsComplete)
			{
				bool iqScan = config.StreamReader.IQScan(0, buffer, (uint)(buffer.Length / 2));
				if (!iqScan)
				{
					result = null;
					logger.Log(PluginLogLevel.Error, "IQScan failed");
					break;
				}
				result.PushPacket(buffer);
			}
			TimeSpan finished = DateTime.Now - started;
			logger.Log(PluginLogLevel.Debug, "Finished IQ acquisiton in {0} ({1} samples per call)", finished.ToString(), buffer.Length / 2);

			config.Ui.OnBlindscanEndIq();

			return result;
		}
		private IqChartData GatherIQGraphCable()
		{
			throw new NotImplementedException();
		}

		private void RunSkyscraper(BlindscanSearchResult result)
		{
			int misCounter = 1;
			bool misMode = false;
			bool cableTvMode = false;
			Caps caps = config.TunerMetadata.Caps;
			SearchResult satelliteSr = new SearchResult();
			SearchResult2 cableSr = new SearchResult2();

			if (caps.HasFlag(Caps.SR_SIGINFO))
			{
				int polarity = result.SearchResult.Pol;
				int freq = result.SearchResult.Freq;
				bool isHighBand = freq > config.LofSw * 1000;
				bool isHorizontal = polarity == 0;

				SendDiseqcCommand(isHighBand, isHorizontal);
				logger.Log(PluginLogLevel.Info, string.Format("Trying to BLScanEx..."), 8);
				if (!config.StreamReader.BLScanEx(result.SearchResult.Freq, 5000, result.SearchResult.Pol,
						config.Lof1 * 1000, config.Lof2 * 1000,
						config.LofSw * 1000, 1000000, (STD_TYPE)result.SearchResult.StdType,
						ref satelliteSr))
				{
					//No blindscan? No problem! Try anyway!
					satelliteSr = result.SearchResult;
					satelliteSr.Lock = false;
					result.State = BlindscanResultState.BlScanFailure;
					config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
				}

				if (!satelliteSr.Lock)
				{
                    int attempts = (result.SearchResult.Freq / 1000).GetHighestDigit();
					bool success = false;
					for (int i = 0; i < attempts; i++)
					{
						Thread.Sleep(1000);
                        result.State = BlindscanResultState.Retrying;
                        logger.Log(PluginLogLevel.Info, string.Format("Retrying BLScanEx... ({0}/{1})", i + 1, attempts + 1), 8);
						bool retryResult = config.StreamReader.BLScanEx(result.SearchResult.Freq, 5000, result.SearchResult.Pol, config.Lof1 * 1000, config.Lof2 * 1000, config.LofSw * 1000, 1000000, (STD_TYPE)result.SearchResult.StdType,ref satelliteSr);
						if (!retryResult)
						{
                            result.State = BlindscanResultState.BlScanFailure;
							continue;
                        }
						if (!satelliteSr.Lock)
						{
							result.State = BlindscanResultState.NoLock;
							continue;
						}
						success = true;
						break;
                    }
                    config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
					if (!success)
						return;
					logger.Log(PluginLogLevel.Debug, "Sucessfully tuned with retries.");
				}

				if (caps.HasFlag(Caps.SR_MISSEL))
				{
					if (satelliteSr.MIS > 16)
						satelliteSr.MIS = 1;

					if (satelliteSr.MIS == 0)
						satelliteSr.MIS = 1;

					if (satelliteSr.MIS != 1)
						misMode = true;

					misCounter = satelliteSr.MIS;
				}
			}
			else if (caps.HasFlag(Caps.SR_SIGINFO2))
			{
				logger.Log(PluginLogLevel.Info, string.Format("Trying to SetChannel2..."), 8);
				bool channel2 = config.StreamReader.SetChannel2((uint)result.SearchResult2.Freq, (uint)result.SearchResult2.BW);
				if (!channel2)
				{
					result.State = BlindscanResultState.TuningFailed;
					config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
					config.Ui.OnBlindscanLockFail(result.SearchResult, result.State);
					return;
				}

				logger.Log(PluginLogLevel.Info, string.Format("Trying to get SignalInfo2..."), 8);
				bool signalInfo2 = config.StreamReader.SignalInfo2(ref cableSr);
				if (!signalInfo2)
				{
					result.State = BlindscanResultState.TuningFailed;
					config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
					config.Ui.OnBlindscanLockFail(result.SearchResult, result.State);
					return;
				}

				if (!cableSr.Lock)
				{
					result.State = BlindscanResultState.NoLock;
					config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
					config.Ui.OnBlindscanLockFail(result.SearchResult, result.State);
					return;
				}
			}
			else
			{
				throw new NotImplementedException("Couldn't figure out what signal info to use.");
			}

			if (config.DoCollectIqGraphs)
			{
				result.State = BlindscanResultState.IqCollecting;
				IqChartData plot = GatherIqGraph();
				if (plot != null)
				{
					result.State = BlindscanResultState.IqSaving;
					config.ObjectStorage.StoreIqGraph(jobInDb.JobGuid, result.GetFrequency(), result.GetPolarity(config.LofSw), plot);
				}
			}

			for (int mis = 0; mis < misCounter; mis++)
			{
				if (misMode)
				{
					logger.Log(PluginLogLevel.Info, string.Format("Selecting MIS IS {0}", satelliteSr.IS[mis]), 8);
					bool misSel = config.StreamReader.MISSel(misMode, satelliteSr.IS[mis], 0xff);
					if (!misSel)
					{
						result.State = BlindscanResultState.MisFailure;
						config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
						config.Ui.OnBlindscanLockFail(result.SearchResult, result.State);
						return;
					}
				}

				//Start Filter
				TsRecorder tsRecorder = null;
				if (config.DoRecordTs)
				{
					tsRecorder = new TsRecorder();
					tsRecorder.Recording = true;
					string outputDirName =  config.Ini.ReadValue("recording", "output_dir", "recording_output");
					tsRecorder.RecordingOutputDirectory = new DirectoryInfo(outputDirName);
					tsRecorder.RecordingOutputDirectory.EnsureExists();
					if (tsRecorder.PrepareRecording())
					{
						DateTime now = DateTime.Now;

						string recordingFilename = string.Format(
							"skyscraper_{0:D4}{1:D2}{2:D2}_{3:D2}{4:D2}_{8}_{5}_{6}_{7}.ts",
							now.Year,                                   //0
							now.Month,                                  //1
							now.Day,                                    //2
							now.Hour,                                   //3
							now.Minute,                                 //4
							result.GetFrequency() / 1000,               //5
							result.GetPolarity(config.LofSw),		    //6
							result.GetSymbolRate() / 1000,              //7
                            config.GetFiveLetterSattelitePosition());   //8
                        tsRecorder.SetNextFilename(recordingFilename);
						tsRecorder.CreateBufferedStream();
					}
				}

				nint filterReference = nint.MaxValue;
				logger.Log(PluginLogLevel.Info, string.Format("Set-Up filter..."), 8);
				packetsQueue = new Queue<byte[]>();
				
				bool filter = config.StreamReader.SetFilter(8192, DvbCallback, 0x02, 2, ref filterReference);
				if (!filter)
				{
					result.State = BlindscanResultState.FilterFailure;
					config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
					config.Ui.OnBlindscanLockFail(result.SearchResult, result.State);
					return;
				}
				logger.Log(PluginLogLevel.Debug, string.Format("Filter set-up complete!"), 8);

				//Use the Filter
				result.State = BlindscanResultState.Scraping;
				config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
				config.Ui.OnBlindscanFilterSetUp();
				
				SkipFilter skipper = new SkipFilter(SkipFilter.CRAZYSCAN_BUFFER * 10);
				skyscraperContext = SkyscraperContextFactory.CreateSkyscraper(config.DataStorage, config.ObjectStorage);
				skyscraperContext.TcpProxyEnabled = true;
				skyscraperContext.UiJunction = config.Ui;
				IPacketFilter[] packetFilters = new IPacketFilter[0];
				if (tsRecorder != null)
					packetFilters = new IPacketFilter[] { tsRecorder, skipper };
				skyscraperContext.InitalizeFilterChain(packetFilters);


				byte[] singlePacketBuffer = new byte[188];
				packetsReceivedInTotal = 0;
				startedAt = DateTime.Now;
				
				//The actual scraping happens in this loop
				while (!StopConditionMet())
				{
					if (!HasPackets())
					{
						Thread.Sleep(100);
						continue;
					}

					byte[] packetBuffer = null;
					lock (packetsQueue)
					{
						packetBuffer = packetsQueue.Dequeue();
					}

					for (int i = 0; i < packetBuffer.Length; i += 188)
					{
						Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188);
						skyscraperContext.IngestSinglePacket(singlePacketBuffer);
					}
				}

				config.Ui.OnBlindscanScrapeStopCondition();
				//Stop Filter
				logger.Log(PluginLogLevel.Debug, string.Format("Deleting Filter..."), 8);
				bool stopped = config.StreamReader.DelFilter(filterReference);
				if (!stopped)
				{
					result.State = BlindscanResultState.DelFilterFailed;
					config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
					return;
				}
				logger.Log(PluginLogLevel.Debug, string.Format("Deleted filter!"), 8);
				DrainPackets();
				skyscraperContext.Dispose();

				if (tsRecorder != null)
					tsRecorder.Dispose();

				result.State = BlindscanResultState.DataSaving;
				config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);

				foreach (HumanReadableService humanReadableService in config.Ui.GetServices())
				{
					config.DataStorage.InsertTransponderService(jobInDb, result.IsSatellite(), result.SearchResult, result.SearchResult2, humanReadableService);
				}

				result.State = BlindscanResultState.Done;
				config.DataStorage.UpdateTransponderState(jobInDb, result.IsSatellite(), result.SearchResult, result.State, result.SearchResult2);
				config.Ui.OnBlindscanAfterPacketDrain();

			}
		}

		private Queue<byte[]> packetsQueue;
		private void DvbCallback(nint data, int length)
		{
			if (length % 188 == 0)
			{
				try
				{
					byte[] buffer = new byte[length];
					Marshal.Copy(data, buffer, 0, length);

					lock (packetsQueue)
					{
						packetsQueue.Enqueue(buffer);
					}

					packetsReceivedInTotal += (uint)(buffer.Length / 188);
					config.Ui.OnBlindscanPacketOk(buffer.Length / 188, packetsQueue.Count);
				}
				catch (OutOfMemoryException e)
				{
					config.Ui.OnBlindscanPacketError(2, length);
				}
			}
			else
			{
				logger.Log(PluginLogLevel.Warn, string.Format("odd packet size!"), 8);
				config.Ui.OnBlindscanPacketError(1, length);
			}
		}

		private ISkyscraperContext skyscraperContext;
		private ulong packetsReceivedInTotal;
		private DateTime startedAt;
		private bool StopConditionMet()
		{
			if (startedAt == DateTime.MinValue)
				startedAt = DateTime.Now;

			if (config.Ui.IsZapNowRequested())
			{
				return true;
			}

			if (!config.Ui.MayAutoZap())
				return false;

			if (packetsReceivedInTotal == 0 && (DateTime.Now - startedAt).TotalSeconds > 5)
				return true;

			if (skyscraperContext.GetIpCommunicationParties() > 1000)
				return true;

			if (!skyscraperContext.EnableTimeout)
			{
				skyscraperContext.TimeoutSeconds = 10;
				skyscraperContext.EnableTimeout = true;
			}

			if (packetsReceivedInTotal > 0 && skyscraperContext.IsAbortConditionMet())
				return true;

			return false;
		}


		private bool HasPackets()
		{
			lock (packetsQueue)
			{
				return packetsQueue.Count > 0;
			}
		}

		private void DrainPackets()
		{
			if (packetsQueue.Count == 0)
				return;

			logger.Log(PluginLogLevel.Warn, string.Format("{0} packets left after switching off the filter.", packetsQueue.Count), 8);

			DateTime startedAt = DateTime.Now;
			byte[] singlePacketBuffer = new byte[188];
			byte[] packetBuffer = null;
			DateTime drainTickerPrevious = DateTime.Now, drainTickerNow = DateTime.Now;
			while (packetsQueue.Count > 0)
			{
				packetBuffer = packetsQueue.Dequeue();
				if (packetBuffer == null)
					continue;
				for (int i = 0; i < packetBuffer.Length; i += 188)
				{
					Array.Copy(packetBuffer, i, singlePacketBuffer, 0, 188);
					skyscraperContext.IngestSinglePacket(singlePacketBuffer);
				}

				drainTickerPrevious = drainTickerNow;
				drainTickerNow = DateTime.Now;
				if (drainTickerNow.Second != drainTickerPrevious.Second)
				{
					logger.Log(PluginLogLevel.Warn, string.Format("{0} packets left ({1}).", packetsQueue.Count, drainTickerNow.ToLongTimeString()), 8);
				}
			}

			logger.Log(PluginLogLevel.Warn, string.Format("Packets drained in {0} seconds.", (DateTime.Now - startedAt).TotalSeconds), 8);
		}
	}
}
