using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Equipment;
using skyscraper5.Skyscraper.Gps;
using skyscraper5.Skyscraper.IO.TunerInterface;
using skyscraper5.Skyscraper.Scraper.Storage.Filesystem;
using skyscraper5.Skyscraper.Scraper.Storage.InMemory;
using skyscraper5.src.Skyscraper;
using skyscraper8.Skyscraper.Plugins;
using skyscraper8.Skyscraper.Scraper.Storage;
using System.IO;
using System.Net.NetworkInformation;

namespace skyscraper8.UI.MonoGame
{
	class Program
	{
		public static void Main(string[] args)
		{
			PluginLogger localLogger = PluginLogManager.GetLogger(typeof(Program));
			SkyscraperHandleCollection handles = new SkyscraperHandleCollection();
			Queue<string> errors = new Queue<string>();

			PerformStartup(localLogger, handles, errors);


			SkyscraperGame game = new SkyscraperGame(handles,errors);

			localLogger.Log(PluginLogLevel.Info,"Entering MonoGame loop...");
			game.Run();
		}

		private static void PerformStartup(PluginLogger localLogger, SkyscraperHandleCollection handles, Queue<string> errors)
		{
			localLogger.Log(PluginLogLevel.Info, "Starting up...");

			//LOAD STORAGE ----------------------------------------------------------------------------------------------------------------------------------------------------------

			localLogger.Log(PluginLogLevel.Info, "Starting Storage Connection Manager...");
			StorageConnectionManager connectionManager = StorageConnectionManager.GetInstance();

			localLogger.Log(PluginLogLevel.Info, "Enumerating Data Storage Factories...");
			handles.AllDataStorages = connectionManager.GetDataStorages().ToList().AsReadOnly();

			localLogger.Log(PluginLogLevel.Debug, "Enumerating Object Storage Factories...");
			handles.AllObjectStorages = connectionManager.GetObjectStorages().ToList().AsReadOnly();

			localLogger.Log(PluginLogLevel.Debug, "Checking for configuration file...");
			if (!connectionManager.IniExists())
			{
				localLogger.Log(PluginLogLevel.Warn, "Configuration file not found!");
				errors.Enqueue(String.Format("The configuration file does not exist.\r\nPlease create one in the UI!"));
				handles.Ini = new Ini();
			}

			DataStorageFactory dataStorageFactory;
			try
			{
				localLogger.Log(PluginLogLevel.Debug, "Getting default data storage factory...");
				dataStorageFactory = connectionManager.GetDefaultDataStorageFactory();
			}
			catch (Exception e)
			{
				localLogger.Log(PluginLogLevel.Error, "Failed to start the data storage factory: {0}", e.Message);
				errors.Enqueue(String.Format("Could not load the data storage factory.\nThe following went wrong:\n\n{0}\n\n",
					e.Message));
				dataStorageFactory = new InMemoryScraperStorageFactory();
			}

			ObjectStorageFactory objectStorageFactory;
			try
			{
				localLogger.Log(PluginLogLevel.Debug, "Getting default object storage factory...");
				objectStorageFactory = connectionManager.GetDefaultObjectStorageFactory();
			}
			catch (Exception e)
			{
				localLogger.Log(PluginLogLevel.Error, "Failed to start the object storage factory: {0}", e.Message);
				errors.Enqueue(String.Format("Could not load the object storage factory.\nThe following went wrong:\n\n{0}\n\n",
					e.Message));
				objectStorageFactory = new FilesystemScraperStorageFactory()
				{
					Directory = "dummy_object_storage"
				};
			}

			localLogger.Log(PluginLogLevel.Info, "Creating Data Storage...");
			handles.DataStorage = dataStorageFactory.CreateDataStorage();


			localLogger.Log(PluginLogLevel.Info, "Checking whether the data storage and the object storage are the same...");
			bool equivalentStorages = objectStorageFactory.IsEquivalent(dataStorageFactory);

			if (equivalentStorages)
			{
				localLogger.Log(PluginLogLevel.Info, "Casting Data Storage to Object Storage.");
				handles.ObjectStorage = (ObjectStorage)handles.DataStorage;
			}
			else
			{
				localLogger.Log(PluginLogLevel.Info, "It isn't -> Creating object storage...");
				try
				{
					handles.ObjectStorage = objectStorageFactory.CreateObjectStorage();
				}
				catch (Exception e)
				{
					string objectStorageName = connectionManager.GetName(objectStorageFactory);
					localLogger.Log(PluginLogLevel.Error, "Failed to start the object storage factory: {0}", e.Message);
					errors.Enqueue(String.Format("Could not load {1}.\nThe following went wrong:\n\n{0}\n\n", e.Message,
						objectStorageName));
				}
			}


			localLogger.Log(PluginLogLevel.Info, "Reporting UI Version to the storages...");
			handles.DataStorage?.UiSetVersion(2);
			handles.ObjectStorage?.UiSetVersion(2);


			localLogger.Log(PluginLogLevel.Info, "Testing whether the data storage is responding...");
			try
			{
				handles.DataStorage.Ping();
			}
			catch (Exception e)
			{
				string brokenStorageName = connectionManager.GetName(dataStorageFactory);
				InMemoryScraperStorageFactory fssf = new InMemoryScraperStorageFactory();
				handles.DataStorage = fssf.CreateDataStorage();
				errors.Enqueue(String.Format(
					"{0} failed to respond.\nThe following went wrong:\n\n{1}\n\nI've switched to a volatile in-memory storage. This will work, but is quite possibly not what you want. It will not and cannot save any data. Please consider configuring {0} correctly.",
					brokenStorageName, e.Message));
			}

			localLogger.Log(PluginLogLevel.Info, "Please wait while I test whether the object storage is responding...");
			try
			{
				handles.ObjectStorage.Ping();
			}
			catch (Exception e)
			{
				string brokenStorageName = connectionManager.GetName(objectStorageFactory);
				FilesystemScraperStorageFactory fssf = new FilesystemScraperStorageFactory();
				fssf.Directory = "dummy_object_storage";
				handles.ObjectStorage = fssf.CreateObjectStorage();
				errors.Enqueue(String.Format(
					"{0} failed to respond.\nThe following went wrong:\n\n{1}\n\nI've switched to a file system based storage. This will work, but is quite possibly not what you want. Please consider configuring {0} correctly.",
					brokenStorageName, e.Message));
			}

			//LOAD GPS ----------------------------------------------------------------------------------------------------------------------------------------------------------------
			localLogger.Log(PluginLogLevel.Info, "Please wait while I load the GPS receiver library...");
			int gpsReceiverId = GpsManager.GetConfiguredGpsId();
			IGpsReceiverFactory gpsReceiverFactory = GpsManager.GetGpsReceiverFactoryById(gpsReceiverId);
			GpsManager.AutoconfigureGpsReceiverFactory(gpsReceiverFactory);

			localLogger.Log(PluginLogLevel.Info, "Instantiating the GPS receiver...");
			handles.Gps = gpsReceiverFactory.CreateGpsReceiver();

			localLogger.Log(PluginLogLevel.Info, "Starting the GPS receiver...");
			handles.Gps.Start();

			//LOAD BASE DATA --------------------------------------------------------------------------------------------------------------------------------------------------------------
			localLogger.Log(PluginLogLevel.Info, "Querying the storage for known satellite positions...");
			handles.SatellitePositions = handles.DataStorage.UiSatellitesListAll();

			localLogger.Log(PluginLogLevel.Info, "Checking for the default LNB types...");
			EquipmentUtilities.InsertDefaultLnbTypes(handles.DataStorage);

			localLogger.Log(PluginLogLevel.Info, "Querying the storage for known LNB types...");
			handles.LnbTypes = handles.DataStorage.UiLnbTypesListAll();

			localLogger.Log(PluginLogLevel.Info, "Checking for the default dish types...");
			EquipmentUtilities.InsertDefaultDishTypes(handles.DataStorage);

			localLogger.Log(PluginLogLevel.Info, "Querying the storage for known Dish types...");
			handles.DishTypes = handles.DataStorage.UiDishTypesListAll();

			//LOAD TUNER --------------------------------------------------------------------------------------------------------------------------------------------------------------------
			localLogger.Log(PluginLogLevel.Info, "Chcking the Tuner Factory classes...");
			TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance();
			handles.AllTunerFactories = tunerFactoryConnectionManager.GetKnownFactories();
			int tunerFactory = handles.Ini.ReadValue("startup", "tunerFactory", 0);
			KeyValuePair<TunerFactoryIdAttribute, ITunerFactory> bootingTuner =
				handles.AllTunerFactories.First(x => x.Key.Id == tunerFactory);
			bool isNoTuner = bootingTuner.Key.DisplayName.Equals("No tuner");
			if (isNoTuner)
			{
				errors.Enqueue(
					"Please not that Skyscraper is currently configured to not use a Tuner Factory Class. This will work, but functionality will be severely limited. Please configure a Tuner Factory Class in order to get the best experience.");
			}

			localLogger.Log(PluginLogLevel.Info, "Applying the tuner factory class configuration..:");
			TunerFactoryConnectionManager.ConfigureFactoryFromIni(bootingTuner, handles.Ini);

			List<TunerMetadata> foundTuners = new List<TunerMetadata>();
			localLogger.Log(PluginLogLevel.Info, "Please wait while the Tuner Factory class code is being executed...");
			try
			{
				handles.StreamReader = bootingTuner.Value.CreateStreamReader();
				try
				{
					localLogger.Log(PluginLogLevel.Info, "Trying to see whether the tuner factory works...");
					handles.StreamReader.CheckForDVB();
				}
				catch (BadImageFormatException e)
				{
					errors.Enqueue(
						"The configured tuner factory is not suitable for the current processor architecture. Tuning won't work, therefore functionality will be severely limited. Please configure a Tuner Factory Class suitable for this processor architecture in order to get the best experience. If you need to run crazycat69's StreamReader.dll on 64-bit machines, try RemoteStreamReader.");
					handles.StreamReader = new NullTunerFactory().CreateStreamReader();
				}

				localLogger.Log(PluginLogLevel.Info, "Checking for your tuners...");
				bool checkForDvbExEx = handles.StreamReader.CheckForDVBExEx((index, name, type) =>
				{
					TunerMetadata tuner = new TunerMetadata(index, name, type);
					localLogger.Log(PluginLogLevel.Info, String.Format("Found tuner {0}", name));
					foundTuners.Add(tuner);
				});
				if (!checkForDvbExEx)
				{
					if (!isNoTuner)
						localLogger.Log(PluginLogLevel.Error,
							String.Format("{0} has failed. Tuning won't be possible!",
								nameof(handles.StreamReader.CheckForDVBExEx)));
				}
			}
			catch (Exception e)
			{
				localLogger.Log(PluginLogLevel.Error, "Oh dear, it failed.");
				errors.Enqueue(
					"Please not that the Tuner Factory class code failed to execute. " +
					"This won't stop the program from working, but functionality will be severely limited. " +
					"Please make sure the Tuner Factory Class is properly configured in order to get the best experience.\n\n" +
					"The following went wrong:\n" + e.Message);
				handles.StreamReader = new NullTunerFactory().CreateStreamReader();
			}


			foreach (TunerMetadata foundTuner in foundTuners)
			{
				handles.StreamReader.StopDVB();

				localLogger.Log(PluginLogLevel.Info, String.Format("Starting tuner {0}", foundTuner.Name));
				bool startDvbEx = handles.StreamReader.StartDvbEx(foundTuner.Index);
				if (!startDvbEx)
				{
					localLogger.Log(PluginLogLevel.Error, String.Format("Failed to start {0}", foundTuner.Name));
					Thread.Sleep(1000);
					continue;
				}

				localLogger.Log(PluginLogLevel.Info, String.Format("Checking capabilities of {0}", foundTuner.Name));
				foundTuner.Caps = handles.StreamReader.GetCaps();

				byte[] macBuffer = new byte[6];
				localLogger.Log(PluginLogLevel.Info, String.Format("Reading MAC Address of {0}", foundTuner.Name));
				bool mac = handles.StreamReader.GetMAC(macBuffer);
				if (!mac)
				{
					localLogger.Log(PluginLogLevel.Error, String.Format("Failed to read MAC Address of {0}", foundTuner.Name));
					Thread.Sleep(1000);
				}
				else
				{
					foundTuner.MacAddress = new PhysicalAddress(macBuffer);
				}

				localLogger.Log(PluginLogLevel.Info, String.Format("Stopping {0}", foundTuner.Name));
				bool stopDvb = handles.StreamReader.StopDVB();
				if (!stopDvb)
				{
					localLogger.Log(PluginLogLevel.Error, String.Format("Failed to stop {0}", foundTuner.Name));
					Thread.Sleep(1000);
					continue;
				}

				localLogger.Log(PluginLogLevel.Info,
					String.Format("Querying storage for configuration of {0}...", foundTuner.Name));
				if (handles.DataStorage.UiTunerTestFor(foundTuner))
				{
					handles.DataStorage.UiTunerGetConfiguration(foundTuner);
				}

				if (handles.Tuners == null)
					handles.Tuners = new List<TunerMetadata>();
				handles.Tuners.Add(foundTuner);
			}

			localLogger.Log(PluginLogLevel.Info, "Checking the engine Version...");
			string engineProductName = handles.StreamReader.GetEngineName();
			Version engineVersion = handles.StreamReader.GetEngineVersion();

			if (!engineProductName.Equals("NullStreamReader"))
			{
				bool finalCaps = handles.StreamReader.CheckForDVB();
				if (!finalCaps)
				{
					errors.Enqueue("Somehow CheckForDVB failed after a second call.");
				}
			}

			localLogger.Log(PluginLogLevel.Info, "Qualifying the engine...");
			QualificationToolResultEnum qualification = QualificationTool.QualifyTunerFactory(engineProductName, engineVersion);
			switch (qualification)
			{
				case QualificationToolResultEnum.Good:
					break;
				case QualificationToolResultEnum.Bad:
					errors.Enqueue(String.Format(
						"You are using {0}, Version {1}\nThis version is known to cause issues with skyscraper. You can continue using it, but if it causes issues, you're on your own.",
						engineProductName, engineVersion));
					break;
				case QualificationToolResultEnum.Unknown:
					errors.Enqueue(String.Format(
						"You are using {0}, Version {1}\nThis version has not been tested with skyscraper, and might cause some issues. Consider submitting a copy of this version to the author.",
						engineProductName, engineVersion));
					break;
				case QualificationToolResultEnum.PossibleIssues:
					errors.Enqueue(String.Format(
						"You are using {0}, Version {1}\nThis version will work, but might have minor issues in some edge cases.",
						engineProductName, engineVersion));
					break;
				default:
					errors.Enqueue(String.Format(
						"You are using {0}, Version {1}\nThe Qualification said \"{2}\", but this status is not implemented.\n Possibly your skyscraper Version and your testdrid Version mismatch?",
						engineProductName, engineVersion, qualification.ToString()));
					break;
			}
		}
	}
}