using Ionic.Zlib;
using log4net;
using log4net.Repository.Hierarchy;
using skyscraper5.DsmCc;
using skyscraper5.DsmCc.Descriptors;
using skyscraper5.DsmCc.Message;
using skyscraper5.Dvb.DataBroadcasting.Biop;
using skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors;
using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs;
using skyscraper5.Dvb.SystemSoftwareUpdate.CarouselIntention;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Scraper;
using skyscraper5.Skyscraper.Scraper.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Tsubasa.IO;

namespace skyscraper5.Dvb.DataBroadcasting
{
    class DataCarouselDecoder : IPsiProcessor, IDisposable
    {
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		private SiDumper siDumper;
        private List<ModuleInfo> waitingModuleInfos;
        private ProgramMappingStream programMappingStream;
        private ProgramMapping programMapping;
        private DataCarouselIntention dataCarouselIntention;
        private DataCarouselEventHandler eventHandler;
        private const bool ENABLE_LOGGING = false;

        public DataCarouselDecoder(ProgramMappingStream _pms, ProgramMapping _pm, DataCarouselIntention _intention, DataCarouselEventHandler _eventHandler)
        {
            siDumper = new SiDumper();
            waitingModuleInfos = new List<ModuleInfo>();
            programMappingStream = _pms;
            programMapping = _pm;
            dataCarouselIntention = _intention;
            eventHandler = _eventHandler;
            if (!programMappingStream.ComponentTag.HasValue)
                throw new NotImplementedException("need a component tag!");
        }

        public void GatherPsi(PsiSection section, int sourcePid)
        {
            //ETSI TR 101 202 V1.2.1 (2003-01) Page 57
            byte[] dataCopy = section.GetDataCopy();
            switch (dataCopy[0])
            {
                case 0x3a:  //LLCSNAP
                    //see tr_101202v010201p.pdf, Page 57
                    ProcessLlcsnap(section, dataCopy);
                    break;
                case 0x3b:  //U-N Message
                    ProcessUNMessage(section, dataCopy);
                    break;
                case 0x3c:
                    ProcessDDIMessage(section, dataCopy);
                    break;
                case 0x3d:
                    ProcessDsmCcDescriptorLength(section, dataCopy, sourcePid);
                    break;
                case 0x3e:  //Private Data
                    //Undocumented, manufacterer specific data.
                    break;
                default:
                    //logger.Warn(String.Format("Unknown DSM-CC Section type: 0x{0:X2}", dataCopy[0]));
                    return;
            }
        }

        private void ProcessLlcsnap(PsiSection section, byte[] data)
        {
            MemoryStream ms = new MemoryStream(data, false);
            uint readUInt24Be = ms.ReadUInt24BE();
            if (readUInt24Be == 0xfefe03)
                throw new NotImplementedException();
            else if (readUInt24Be == 0xaaaa03)
                throw new NotImplementedException();
        }

        private void ProcessDsmCcDescriptorLength(PsiSection section, byte[] dataCopy, int sourcePid)
        {
            if (!section.Valid)
                return;
            //see tr_101202v010201p.pdf, Page 57
            DsmCcSection descriptorList = new DsmCcSection(new MemoryStream(dataCopy, false), 0x3d);
            DsmCcDescriptorList unpackedList = (DsmCcDescriptorList)descriptorList.DsmCcMessage;
            if (unpackedList == null)
                return;

            if (unpackedList.Error != null)
                return;

            if (unpackedList.StreamEventDescriptors.Count == 0)
                return;

            foreach (StreamEventDescriptor descriptorListStreamEventDescriptor in unpackedList.StreamEventDescriptors)
            {
                eventHandler.DsmCcDoItNowEvent(programMapping, descriptorListStreamEventDescriptor, sourcePid);
            }
            
        }

        private void ProcessUNMessage(PsiSection section, byte[] dataCopy)
        {
            if (!section.Validate())
                return;
            DsmCcSection section3b = new DsmCcSection(new MemoryStream(dataCopy, false), 0x3b);
            if (section3b.DsmCcMessage == null)
                return;

            switch (section3b.DsmCcMessage.Header.MessageId)
            {
                case 0x1002:
                    ProcessDownloadInfoIndication((DownloadInfoIndication)section3b.DsmCcMessage);
                    break;
                case 0x1006:
                    ProcessDownloadServerInitiate((DownloadServerInitiate)section3b.DsmCcMessage);
                    break;
                default:
                    throw new NotImplementedException(String.Format("UNM: {0} 0x{1:X4}", nameof(section3b.DsmCcMessage.Header.MessageId), section3b.DsmCcMessage.Header.MessageId));
            }
        }

        private void ProcessDownloadInfoIndication(DownloadInfoIndication section3BDsmCcMessage)
        {
            if (section3BDsmCcMessage.Modules == null)
                return;
            if (!section3BDsmCcMessage.Valid)
                return;
            if (section3BDsmCcMessage.BlockSize == 0)
                return;

            ModuleInfo[] moduleInfos = Array.ConvertAll(section3BDsmCcMessage.Modules, x => new ModuleInfo(x, section3BDsmCcMessage, programMapping, programMappingStream.ComponentTag.Value));
            Log("DSM-CC: {0:X8} Got Download Info Indication", section3BDsmCcMessage.Header.TransactionId);
            foreach (ModuleInfo moduleInfo in moduleInfos)
            {
                if (waitingModuleInfos.Contains(moduleInfo))
                    continue;

                if (!eventHandler.IsModuleWanted(programMappingStream.ElementaryPid, moduleInfo.ModuleId, moduleInfo.ModuleVersion))
                    continue;
                
                waitingModuleInfos.Add(moduleInfo);
                Log("DSM-CC: {0:X8} - Module {1:X4}, Version {2:X2}, Num Blocks: {3:X4}", section3BDsmCcMessage.Header.TransactionId, moduleInfo.ModuleId, moduleInfo.ModuleVersion, moduleInfo.ModuleSize / section3BDsmCcMessage.BlockSize);
                if (!moduleInfo.LooksLikeObjectCarousel)
                    eventHandler.NotifyOfNewModule(programMappingStream.ElementaryPid, moduleInfo.ModuleId, moduleInfo.ModuleVersion);
            }
        }
        
        private void ProcessDDIMessage(PsiSection section, byte[] dataCopy)
        {
            if (!section.Validate())
                return;
            DsmCcSection section3c = new DsmCcSection(new MemoryStream(dataCopy, false), 0x3c);
            if (section3c.DsmCcMessage == null)
                return;
            switch (section3c.DsmCcMessage.Header.MessageId)
            {
                case 0x1003:
                    ProcessDownloadDataBlock((DownloadDataBlock)section3c.DsmCcMessage);
                    break;
                default:
                    throw new NotImplementedException(String.Format("DDI: {0} 0x{1:X4}", nameof(section3c.DsmCcMessage.Header.MessageId), section3c.DsmCcMessage.Header.MessageId));
            }
        }
        
        private void ProcessDownloadDataBlock(DownloadDataBlock block)
        {
            //We got a downloaded data block, now what?
            Log("DSM-CC: {0:X8} - Download Data Block", block.DownloadId);
            ModuleInfo moduleInfo = waitingModuleInfos.Find(x => x.ModuleId == block.ModuleId && x.ModuleVersion == block.ModuleVersion);
            if (moduleInfo == null)
            {
                return;
            }

            moduleInfo.Blocks[block.BlockNumber] = block;
            if (moduleInfo.BlocksComplete)
            {
	            try
	            {
		            AssembleModule(moduleInfo);
		            waitingModuleInfos.Remove(moduleInfo);
				}
	            catch (ZlibException zlibE)
	            {
		            ForgetModule(moduleInfo, true, true);
		            Debug.WriteLine("A DSM-CC module was corrupted in transit.");
	            }
            }
            else if (moduleInfo.TaggedProgress < moduleInfo.DownloadProgress)
            {
                eventHandler.NotifyModuleDownloadProgress(programMappingStream.ElementaryPid, moduleInfo.ModuleId, moduleInfo.ModuleVersion, moduleInfo.DownloadProgress);
                moduleInfo.TaggedProgress = moduleInfo.DownloadProgress;
            }
        }
        
        private ObjectCarouselMetadata vfs;
        private void AssembleModule(ModuleInfo module)
        {
            ModuleInfoStream assembledStream = new ModuleInfoStream(module, true);
            bool isObjectCarousel = ObjectCarouselUtilities.TestForObjectCarousel(assembledStream, module.CompressedModuleDescriptor);
            assembledStream.Position = 0;
            if (isObjectCarousel)
            {
                if (vfs == null)
                    vfs = new ObjectCarouselMetadata(eventHandler.GetObjectCarouselEventHandler(), programMappingStream.ElementaryPid, programMapping);
                vfs.LastExtractions = 0;
                vfs.DirectoriesCreated = 0;
                ObjectCarouselUtilities.ExtractBiopModule(module, assembledStream, vfs);
                if (vfs.DirectoriesCreated != 0)
                {
                    eventHandler.PreventDsmCcModuleRepetition(programMappingStream.ElementaryPid, module.ModuleId, module.ModuleVersion);
                    eventHandler.ProcessVirtualFilesystem((IFilesystem)vfs, programMappingStream);
                }
                return;
            }
            eventHandler.NotifyDownloadComplete(programMappingStream.ElementaryPid, module.ModuleId, module.ModuleVersion, assembledStream, isObjectCarousel);
            //assembledStream.Close();

            ForgetModule(module, true, false);
        }

        private void ProcessDownloadServerInitiate(DownloadServerInitiate downloadServerInitiate)
        {
            if (downloadServerInitiate.PrivateData == null)
                return;

            byte[] buffer = downloadServerInitiate.PrivateData;
            switch (dataCarouselIntention)
            {
                case DataCarouselIntention.SoftwareUpdate:
                    GroupInfoIndication gii = new GroupInfoIndication(buffer);
                    break;
                default:
                    /*
                    bool isObjectCarousel = ObjectCarouselUtilities.TestForObjectCarousel(downloadServerInitiate);
                    if (isObjectCarousel)
                        break;
                    File.WriteAllBytes("buffer.bin", buffer);
                    throw new NotImplementedException(dataCarouselIntention.ToString());
                    */
                    break;
            }
        }

        private void Log(string fmt, params object[] args)
        {
            if (ENABLE_LOGGING)
            {
                Console.WriteLine(fmt, args);
            }
        }

        private void ForgetModule(ModuleInfo mi, bool removeWaiting = true, bool clearBlocks = true)
        {
            if (removeWaiting)
            {
                waitingModuleInfos.Remove(mi);
            }

            if (mi.Blocks == null)
                return;

            if (clearBlocks)
            {
                for (int i = 0; i < mi.Blocks.Length; i++)
                {
                    if (mi.Blocks[i] != null)
                    {
                        mi.Blocks[i].ForgetBlock();
                        mi.Blocks[i] = null;
                    }
                }
            }
        }

        public void Dispose()
        {
            while (waitingModuleInfos.Count > 0)
            {
                ForgetModule(waitingModuleInfos[0], true, false);
            }
        }
    }
}
