using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.IO.CrazycatStreamReader;
using skyscraper5.Skyscraper.IO.TunerInterface;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.Skyscraper.Scraper;
using skyscraper5.Skyscraper.Scraper.Storage;
using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
using Console = System.Console;

namespace skyscraper5.Skyscraper
{
	internal class CableFrequencyListGenerator
	{
		public CableFrequencyListGenerator()
		{

		}

		public void Run()
		{
			Ini ini = PluginManager.GetInstance().Ini;
			TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance();
			ITunerFactory tunerFactory = tunerFactoryConnectionManager.AutoGetConfiguredTunerFactory();
			IStreamReader streamReader = tunerFactory.CreateStreamReader();
			int usableTuner = -1;
			if (!streamReader.CheckForDVBExEx((x, y, z) =>
			    {
					if (usableTuner == -1 && z == STD_TYPE.STD_DVBC)
					    usableTuner = x;
			    }))
			{
				Console.WriteLine("Failed to check for tuners.");
				return;
			}
			if (usableTuner == -1)
			{
				Console.WriteLine("no dvb-c tuner present, quitting.");
				return;
			}

			if (!streamReader.StartDvbEx(usableTuner))
			{
				Console.WriteLine("Failed to start the tuner.");
				streamReader.StopDVB();
				if (!streamReader.StartDvbEx(usableTuner))
				{
					Console.WriteLine("Giving up...");
					return;
				}
			}

			IntPtr pSearchResult = Marshal.AllocHGlobal(ushort.MaxValue);
			int tpNum = 0;
			SearchResult2 sr2 = default;
			if (!streamReader.AirScan(48000, 1000000, 3000, 8000, (int)STD_TYPE.STD_DVBC, pSearchResult, ref tpNum, (ref SearchResult2 searchResult) => SearchResult2Callback(searchResult)))
			{
				Console.WriteLine("Failed to run AirScan...");
				return;
			}

			string filename = String.Format("dvb-c_{0}.csv", DateTime.Now.ToUnixTime());
			FileStream fileStream = File.OpenWrite(filename);
			StreamWriter streamWriter = new StreamWriter(fileStream);

			foreach (SearchResult2 searchResult2 in searchResults)
			{
				double outFreq = searchResult2.Freq;
				outFreq = Math.Round((double)outFreq / (double)1000.0, 0) * 1000;
				outFreq /= 1000;

				Console.WriteLine("Tuning to {0} Mhz", outFreq);
				if (!streamReader.SetChannel2((uint)searchResult2.Freq, (uint)searchResult2.BW))
				{
					Console.WriteLine("Failed to tune.");
					continue;
				}

				SearchResult2 recheck = default;
				if (!streamReader.SignalInfo2(ref recheck))
				{
					Console.WriteLine("Failed to get signal info.");
					continue;
				}

				if (!recheck.Lock)
				{
					Console.WriteLine("No lock.");
					continue;
				}

				double outSymRate = searchResult2.SR;
				outSymRate = Math.Round((double)outSymRate / (double)10000.0, 0) * 10000;
				outSymRate /= 1000;
				string outModType = ((MOD_TYPE)searchResult2.ModType).ToString();
				string outLine = String.Format("{0} Mhz;{1} kSym/s;{2}", outFreq, outSymRate, outModType);
				streamWriter.WriteLine(outLine);

				StreamReaderScraperController scraper = new StreamReaderScraperController(streamReader);
				scraper.ScraperStroage = new InMemoryScraperStorage();
				scraper.Recording = false;
				Console.Write("Running PSI acquisition...");
				scraper.Run();
				Console.WriteLine(".DONE");

				IScraperStroage data = scraper.ScraperStroage;
				IEnumerable<Tuple<int, int, ProgramMapping>> pmtEntries = data.SelectAllPmt().ToList();
				List<Tuple<int, int, SdtService>> tuples = data.SelectAllSdt().ToList();
				foreach (var (tsId, networkId, programMapping) in pmtEntries)
				{
					SdtService sdt = data.SelectSdtById(networkId, tsId, programMapping.ProgramNumber);
					if (sdt == null)
					{
						continue;
					}
					outLine = String.Format("{0};{1};{2};{3};{4}", programMapping.ProgramNumber,sdt.ServiceProviderName,sdt.ServiceName,GetCaSystemName(programMapping,sdt),sdt.ServiceType);
					streamWriter.WriteLine(outLine);
					streamWriter.Flush();
				}
				streamWriter.WriteLine("");

			}

			if (!streamReader.StopDVB())
			{
				Console.WriteLine("Failed to properly close the tuner!");
				return;
			}
			streamWriter.Flush();
			streamWriter.Close();
		}

		private string GetCaSystemName(ProgramMapping pmt, SdtService sdt)
		{
			if (pmt.CaSystemId.HasValue)
			{
				return CaSystemNames.GetHumanReadableName(pmt.CaSystemId.Value);
			}

			if (sdt.CaIdentifiers != null)
			{
				if (sdt.CaIdentifiers.Length > 0)
				{
					return CaSystemNames.GetHumanReadableName(sdt.CaIdentifiers[0]);
				}
			}

			return "";
		}

		private List<SearchResult2> searchResults;
		private void SearchResult2Callback(SearchResult2 searchResult)
		{
			if (searchResults == null)
				searchResults = new List<SearchResult2>();
			searchResults.Add(searchResult);
			Console.WriteLine("Heard something at {0}", searchResult.Freq / 1000);
		}

	}
}
