using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using log4net;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Headless;
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 skyscraper8.Skyscraper.Scraper.Storage;

namespace skyscraper5
{
    public class Passing
    {
        public DataStorage DataStorage { get; set; }
        public ObjectStorage ObjectStorage { get; set; }
        private IStreamReader streamReader;
        private List<TunerMetadata> tuners;
        private List<SatellitePosition> satellitePositions;
        private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		public Passing()
        {

        }

        public bool Boot()
        {

	        Ini ini = PluginManager.GetInstance().Ini;
            
            StorageConnectionManager connectionManager = StorageConnectionManager.GetInstance();

            IEnumerable<Tuple<int, bool, string>> allKnownFactoryNames = connectionManager.GetAllKnownFactoryNames();
            foreach (Tuple<int, bool, string> knownFactoryName in allKnownFactoryNames)
            {
                logger.InfoFormat("Found {0} Storage Factory #{1}, {2}",knownFactoryName.Item2 ? "Data" : "Object",knownFactoryName.Item1,knownFactoryName.Item3);
            }

            logger.Debug("Acquiring Default Data Storage...");
            DataStorageFactory dataStorageFactory = connectionManager.GetDefaultDataStorageFactory();
            logger.Debug("Acquiring Default Object Storage...");
            ObjectStorageFactory objectStorageFactory = connectionManager.GetDefaultObjectStorageFactory();
            logger.Debug("Acquiring Data Storage...");
            DataStorage = dataStorageFactory.CreateDataStorage();
            if (DataStorage == null)
            {
	            logger.FatalFormat("The data storage factory didn't create a data storage.");
	            return false;
            }

            bool isEquivalent = objectStorageFactory.IsEquivalent(dataStorageFactory);
            if (isEquivalent)
            {
	            logger.Debug("Using the Data Storage as Object Storage.");
	            ObjectStorage = (ObjectStorage)DataStorage;
            }
            else
            {
	            logger.Debug("Acquiring Object Storage...");
	            ObjectStorage = objectStorageFactory.CreateObjectStorage();
	            if (ObjectStorage == null)
	            {
		            logger.FatalFormat("The object storage factory didn't create an object storage.");
		            return false;
	            }
            }
            
            TunerFactoryConnectionManager tunerFactoryConnectionManager = TunerFactoryConnectionManager.GetInstance();
            ReadOnlyCollection<KeyValuePair<TunerFactoryIdAttribute, ITunerFactory>> tunerFactories = tunerFactoryConnectionManager.GetKnownFactories();
            int tunerFactory = ini.ReadValue("startup", "tunerFactory", 0);
            KeyValuePair<TunerFactoryIdAttribute, ITunerFactory> bootingTuner = tunerFactories.First(x => x.Key.Id == tunerFactory);
            if (bootingTuner.Value == null)
            {
                Console.WriteLine("The tuner factory #{0} didn't load.", tunerFactory);
                return false;
            }

            TunerFactoryConnectionManager.ConfigureFactoryFromIni(bootingTuner, ini);

            streamReader = bootingTuner.Value.CreateStreamReader();
            if (streamReader == null)
            {
                Console.WriteLine("The tuner factory #{0} didn't create a stream reader.");
                return false;
            }

            try
            {
                streamReader.CheckForDVB();
            }
            catch (BadImageFormatException e)
            {
                Console.WriteLine("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.");
                streamReader = new NullTunerFactory().CreateStreamReader();
            }
            
            List<TunerMetadata> foundTuners = new List<TunerMetadata>();
            bool checkForDvbExEx = streamReader.CheckForDVBExEx((index, name, type) =>
            {
                TunerMetadata tuner = new TunerMetadata(index, name, type);
                foundTuners.Add(tuner);
            });

            foreach (TunerMetadata foundTuner in foundTuners)
            {
                streamReader.StopDVB();
                
                bool startDvbEx = streamReader.StartDvbEx(foundTuner.Index);
                if (!startDvbEx)
                {
                    Console.WriteLine("Failed to start {0}", foundTuner.Name);
                    Thread.Sleep(1000);
                    continue;
                }
                
                foundTuner.Caps = streamReader.GetCaps();

                byte[] macBuffer = new byte[6];
                bool mac = streamReader.GetMAC(macBuffer);
                if (!mac)
                {
                    Console.WriteLine("Failed to read MAC Address of {0}", foundTuner.Name);
                    Thread.Sleep(1000);
                }
                else
                {
                    foundTuner.MacAddress = new PhysicalAddress(macBuffer);
                }
                
                bool stopDvb = streamReader.StopDVB();
                if (!stopDvb)
                {
                    Console.WriteLine("Failed to stop {0}", foundTuner.Name);
                    Thread.Sleep(1000);
                    continue;
                }
;
                if (DataStorage.UiTunerTestFor(foundTuner))
                {
                    DataStorage.UiTunerGetConfiguration(foundTuner);
                }

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

            satellitePositions = DataStorage.UiSatellitesListAll();

            return true;
        }

        private HeadlessJob GetNextJob()
        {
            HeadlessJob headlessJob = DataStorage.GetQueuedJob();
            if (headlessJob != null)
            {
                return headlessJob;
            }

            return new HeadlessJob(HeadlessJobType.BlindscanEverything);
        }

        public void Run()
        {
            while (true)
            {
                HeadlessJob headlessJob = GetNextJob();
                Run(headlessJob);
                if (!headlessJob.isSynthetic)
                {
                    DataStorage.SetQueuedJobComplete(headlessJob);
                }
            }
        }

        private void Run(HeadlessJob job)
        {
            switch (job.jobType)
            {
                case HeadlessJobType.Sleep:
                    Thread.Sleep(job.iArg1);
                    break;
                case HeadlessJobType.MassImport:
                    DirectoryInfo di = new DirectoryInfo(job.sArg1);
                    MassImportDirectory(di);
                    break;
                case HeadlessJobType.BlindscanEverything:
                    PerformBlindscanEverything();
                    break;
                case HeadlessJobType.ReimportByTag1:
                    ReimportTag(job.iArg1);
                    break;
                default:
                    throw new NotImplementedException(job.jobType.ToString());
            }
        }

        private void PerformBlindscanEverything()
        {
            if (tuners == null)
                tuners = new List<TunerMetadata>();
            if (tuners.Count == 0)
                throw new ApplicationException("I'd need to blindscan everything, but I don't have any tuners I could use!");

            throw new NotImplementedException(nameof(PerformBlindscanEverything));
        }

        private void ReimportTag(int tag1)
        {
            IReadOnlyList<string> queue = DataStorage.ListImportFileByTag1(tag1);
            foreach(string filename in queue)
            {
                FileInfo fi = new FileInfo(filename);
                if (!fi.Exists)
                    continue;
                ScrapeStream(fi);
            }
        }

        public void MassImportDirectory(DirectoryInfo sourceDir)
        {
            foreach (FileSystemInfo fileSystemInfo in sourceDir.GetFileSystemInfos())
            { 
                DirectoryInfo di = fileSystemInfo as DirectoryInfo;
                FileInfo fi = fileSystemInfo as FileInfo;
                if (di != null)
                {
                    MassImportDirectory(di);
                }
                else if (fi != null)
                {
                    if (fi.Length == 0)
                        continue;

                    if (!fi.Extension.ToLowerInvariant().Equals(".ts"))
                        continue;

                    if (DataStorage.ImportFileKnown(fi))
                        continue;

                    FileStream fileStream = fi.OpenRead();
                    Stopwatch stopwatch = Stopwatch.StartNew();
                    int tstype;
                    ScrapeStream(fileStream,true, out tstype);
                    stopwatch.Stop();
                    Console.WriteLine("Importing {0} took {1}",fi.Name, stopwatch.Elapsed);
                    DataStorage.WaitForCompletion();
                    DataStorage.ImportMarkFileAsKnown(fi, stopwatch.Elapsed, tstype);
                    fileStream.Close();
                }
                else
                {
                    throw new NotImplementedException(fileSystemInfo.GetType().ToString());
                }
            }
        }

        private void ScrapeStream(FileInfo fi)
        {
			FileStream fileStream = fi.OpenRead();
            ScrapeStream(fileStream, true, out _);
            fileStream.Close();
            fileStream.Dispose();
        }

        private void ScrapeStream(Stream inStream, bool disk, out int tstype)
        {
            SkyscraperContext skyscraperContext = new SkyscraperContext(new TsContext(), DataStorage,ObjectStorage);
            skyscraperContext.SourceIsDisk = disk;
            skyscraperContext.InitalizeFilterChain();
            skyscraperContext.IngestFromStream(inStream);
            skyscraperContext.Dispose();
            tstype = skyscraperContext.SpecialTsType;
        }
    }
}
