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 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;

namespace skyscraper5
{
    public class Passing
    {
	    public IScraperStroage ScraperStorage { get; set; }
        private IStreamReader streamReader;
        private List<TunerMetadata> tuners;
        private List<SatellitePosition> satellitePositions;

        public Passing()
        {

        }

        public bool Boot()
        {

	        Ini ini = PluginManager.GetInstance().Ini;

            int requiredFactoryId = ini.ReadValue("startup", "storage", -1);
            if (requiredFactoryId == -1)
            {
                Console.WriteLine("Could not determine storage factory from ini file. Configure it using the UI!");
                return false;
            }

            ScraperStorageFactoryConnectionManager connectionManager = ScraperStorageFactoryConnectionManager.GetInstance();

            DirectoryInfo di = new DirectoryInfo(".");
            FileInfo[] fileInfos = di.GetFiles("skyscraper5.Storage.*.dll");
            
            foreach (KeyValuePair<ScrapeStorageFactoryIdAttribute, IScraperStorageFactory> keyValuePair in connectionManager.GetKnownFactories())
            {
                Console.WriteLine("Found Storage Factory #{0}, {1}", keyValuePair.Key.Id, keyValuePair.Key.DisplayName);
            }

            string iniCategoryName = String.Format("storage{0}", requiredFactoryId);
            if (!ini.ContainsKey(iniCategoryName))
            {
                Console.WriteLine("Could not find configuration for the storage factory. Configure it using the UI!");
                return false;
            }

            KeyValuePair<ScrapeStorageFactoryIdAttribute, IScraperStorageFactory> valuePair = connectionManager.GetKnownFactories().First(x => x.Key.Id == requiredFactoryId);
            if (valuePair.Key == null)
            {
                Console.WriteLine("Storage Factory {0} was not loaded. Reconfigure with the UI!", requiredFactoryId);
                return false;
            }
            
            string factoryCname = String.Format("storage{0}", valuePair.Key.Id);
            PluginManager.GetInstance().AutoconfigureObject(factoryCname, valuePair.Value);

            Console.WriteLine("Booting {0}...", valuePair.Key.DisplayName);
            ScraperStorage = valuePair.Value.CreateScraperStroage();
            if (ScraperStorage == null)
            {
                Console.WriteLine("The storage factory didn't create a 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 (ScraperStorage.UiTunerTestFor(foundTuner))
                {
                    ScraperStorage.UiTunerGetConfiguration(foundTuner);
                }

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

            satellitePositions = ScraperStorage.UiSatellitesListAll();

            return true;
        }

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

            return new HeadlessJob(HeadlessJobType.BlindscanEverything);
        }

        public void Run()
        {
            while (true)
            {
                HeadlessJob headlessJob = GetNextJob();
                Run(headlessJob);
                if (!headlessJob.isSynthetic)
                {
                    ScraperStorage.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 = ScraperStorage.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 (ScraperStorage.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);
                    ScraperStorage.WaitForCompletion();
                    ScraperStorage.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(), ScraperStorage);
            skyscraperContext.SourceIsDisk = disk;
            skyscraperContext.InitalizeFilterChain();
            skyscraperContext.IngestFromStream(inStream);
            skyscraperContext.Dispose();
            tstype = skyscraperContext.SpecialTsType;
        }
    }
}
