using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ionic.Zlib;
using skyscraper5.DsmCc;
using skyscraper5.DsmCc.Message;
using skyscraper5.Dvb.DataBroadcasting.Biop;
using skyscraper5.Dvb.DataBroadcasting.Biop.TapSelectors;
using skyscraper5.Dvb.DataBroadcasting.DataCarouselDescriptors;
using skyscraper5.Dvb.DataBroadcasting.Dsm.Event;
using skyscraper5.Dvb.DataBroadcasting.Dsm.Stream;
using skyscraper5.Dvb.DataBroadcasting.SkyscraperVfs;
using skyscraper5.Mpeg2.Psi.Model;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Dvb.DataBroadcasting
{
    internal static class ObjectCarouselUtilities
    {
        public static bool TestForObjectCarousel(DownloadServerInitiate dsi)
        {
            byte[] buffer = dsi.PrivateData;
            if (buffer[4] != 0x73)
                return false;
            if (buffer[5] != 0x72)
                return false;
            if (buffer[6] != 0x67)
                return false;

            InteroperableObjectReference interoperableObjectReference = new InteroperableObjectReference(dsi.PrivateData);
            if (interoperableObjectReference.TaggedProfiles == null && interoperableObjectReference.TypeId == null)
                return false;
            switch (interoperableObjectReference.TypeId)
            {
                case "srg":
                    break;
                default:
                    throw new NotImplementedException(interoperableObjectReference.TypeId);
            }
            bool profileProcessed = interoperableObjectReference.TaggedProfiles.Length == 0;

            foreach (TaggedProfile taggedProfile in interoperableObjectReference.TaggedProfiles)
            {
                switch (taggedProfile.ProfileIdTag)
                {
                    case 0x49534f06:    //TAG_BIOP (BIOP Profile Body)
                        BiopProfileBody biopProfileBody = new BiopProfileBody(taggedProfile.ProfileData);
                        profileProcessed = true;
                        SelectorType_0x01 selectorType0X01 = (SelectorType_0x01)biopProfileBody.ConnBinder.OriginTap.Selector;
                        Debug.WriteLine("DSM-CC: {0:X8} Got Download Server Initiate. The selector says to look for {1:x8}", dsi.Header.TransactionId, selectorType0X01.TransactionId);
                        break;
                    default:
                        throw new NotImplementedException(String.Format("{0:X8", taggedProfile.ProfileIdTag));
                }
            }

            if (!profileProcessed)
                throw new DsmCcException("Failed to find processable IOP Profile in DSM-CC message.");

            Debug.WriteLine("DSM-CC: {0:X8} Got Download Server Initiate", dsi.Header.TransactionId);
            return true;
        }

        public static bool TestForObjectCarousel(Stream ms, CompressedModuleDescriptor moduleCompressedModuleDescriptor)
        {
            if (moduleCompressedModuleDescriptor != null)
            { 
                switch (moduleCompressedModuleDescriptor.CompressionMethod)
                {
                    case (byte)'x':
                        ms = new Ionic.Zlib.ZlibStream(ms, CompressionMode.Decompress);
                        break;
                    case 0x08:
                        ms = new Ionic.Zlib.ZlibStream(ms, CompressionMode.Decompress);
                        break;
                    default:
                        ms.Close();
                        ms.Dispose();
                        return false;
                }
            }

            uint magic = ms.ReadUInt32BE();
            return magic == 0x42494F50;
        }

        public static void ExtractBiopModule(ModuleInfo module, Stream compressedBuffer, ObjectCarouselMetadata vfs)
        {
            bool zlib = false;
            Stream uncompressedBuffer;
            if (module.CompressedModuleDescriptor != null)
            {
                switch (module.CompressedModuleDescriptor.CompressionMethod)
                {
                    case (byte)'x':
                        uncompressedBuffer = new Ionic.Zlib.ZlibStream(compressedBuffer, CompressionMode.Decompress);
                        zlib = true;
                        break;
                    case 0x08:
                        uncompressedBuffer = new Ionic.Zlib.ZlibStream(compressedBuffer, CompressionMode.Decompress);
                        zlib = true;
                        break;
                    default:
                        //File.WriteAllBytes(string.Format("unknown_compressor_0x{0:X2}.bin", module.CompressedModuleDescriptor.CompressionMethod), compressedBuffer); ;
                        throw new NotImplementedException(nameof(module.CompressedModuleDescriptor));
                }
            }
            else
            {
                uncompressedBuffer = compressedBuffer;
            }

            Stream ms = uncompressedBuffer;
            uint magicNumber = ms.ReadUInt32BE();
            if (zlib)
            {
                compressedBuffer.Position = 0;
                uncompressedBuffer = new Ionic.Zlib.ZlibStream(compressedBuffer, CompressionMode.Decompress);
                ms = new MemoryStream();
                uncompressedBuffer.CopyTo(ms);
                ms.Position = 0;
            }
            else
            {
                ms.Position = 0;
            }
            switch (magicNumber)
            {
                case 0x42494F50:    //BIOP
                    AssembleBiopModule(ms, module, vfs);
                    break;
                default:
                    throw new NotImplementedException(String.Format("{0:X8}", magicNumber));
            }
        }

        private static void AssembleBiopModule(Stream ms, ModuleInfo module, ObjectCarouselMetadata vfs)
        {
            while (true)
            {
                if (ms.Position == ms.Length)
                    break;
                uint magicNumber = ms.ReadUInt32BE();
                if (magicNumber != 0x42494F50)
                    throw new NotImplementedException(String.Format("{0:X8}", magicNumber));

                byte biop_version_major = ms.ReadUInt8();
                byte biop_version_minor = ms.ReadUInt8();
                byte byte_order = ms.ReadUInt8();
                if (byte_order != 0x00)
                    throw new NotImplementedException("little endian");

                byte message_type = ms.ReadUInt8();
                if (message_type != 0x00)
                    throw new NotImplementedException(String.Format("{0} {0:X2}", nameof(message_type), message_type));

                uint message_size = ms.ReadUInt32BE();
                if (ms.GetAvailableBytes() < message_size)
                    break;
                MemoryStream messageMs = new MemoryStream(ms.ReadBytes(message_size), false);

                byte objectKeyLength = messageMs.ReadUInt8();
                byte[] objectKeyData = messageMs.ReadBytes(objectKeyLength);

                uint objectKindLength = messageMs.ReadUInt32BE();
                string objectKindData = Encoding.ASCII.GetString(messageMs.ReadBytes(objectKindLength)).TrimEnd((char)0);

                switch (objectKindData)
                {
                    case "fil":
                        DecodeFileMessage(messageMs, module, objectKeyData, vfs);
                        break;
                    case "srg":
                        VfsDirectory newRoot = DecodeServiceRegistryMessage(messageMs, module, objectKeyData, vfs);
                        if (vfs.rootDirectory == null)
                        {
                            vfs.rootDirectory = newRoot;
                            vfs.vfsDirectories.Add(vfs.rootDirectory);
                        }
                        vfs.DirectoriesCreated++;
                        break;
                    case "dir":
                        VfsDirectory outputDir = vfs.vfsDirectories.Find(x => x.MatchObjectKey(objectKeyData));
                        if (outputDir != null && !outputDir.ContainsVfsEntries)
                        {
                            DecodeServiceRegistryMessage(messageMs, module, objectKeyData, vfs, outputDir);
                            vfs.DirectoriesCreated++;
                        }
                        break;
                    case "ste":
                        DecodeStreamEvent(messageMs, module, objectKeyData, vfs);
                        break;
                    default:
                        throw new NotImplementedException(objectKindData);
                }
            }
        }

        private static void DecodeFileMessage(MemoryStream ms, ModuleInfo module, byte[] objectKeyData, ObjectCarouselMetadata vfs)
        {
            ushort objectInfoLength = ms.ReadUInt16BE();
            ulong contentSize = ms.ReadUInt64BE();
            objectInfoLength -= 8;
            byte[] objectInfoData = ms.ReadBytes(objectInfoLength);

            byte serviceContextListCount = ms.ReadUInt8();
            Dictionary<uint, byte[]> serviceContexts = null;
            for (byte i = 0; i < serviceContextListCount; i++)
            {
                uint contextId = ms.ReadUInt32BE();
                ushort contextDataLength = ms.ReadUInt16BE();
                byte[] contextData = ms.ReadBytes(contextDataLength);
                if (serviceContexts == null)
                    serviceContexts = new Dictionary<uint, byte[]>();
                serviceContexts.Add(contextId, contextData);
            }

            uint messageBodyLength = ms.ReadUInt32BE();
            uint contentLength = ms.ReadUInt32BE();
            byte[] contentData = ms.ReadBytes(contentLength);

            VfsFile vfsFile = vfs.vfsFiles.Find(x => x.MatchObjectKey(objectKeyData));
            if (vfsFile != null)
            {
                vfsFile.FileContent = contentData;
                vfs.EventHandler.NotifyFileArrival(vfsFile);
                vfs.LastExtractions++;
            }
            else
            {
                return;
            }
        }

        private static VfsDirectory DecodeServiceRegistryMessage(MemoryStream ms, ModuleInfo module, byte[] objectKeyData, ObjectCarouselMetadata vfs, VfsDirectory copyFrom = null)
        {
            VfsDirectory result = new VfsDirectory();
            if (copyFrom != null)
                result = copyFrom;
            ushort objectInfoLength = ms.ReadUInt16BE();
            byte[] objectInfoData = ms.ReadBytes(objectInfoLength);

            byte serviceContextListCount = ms.ReadUInt8();
            Dictionary<uint, byte[]> serviceContexts = null;
            for (int i = 0; i < serviceContextListCount; i++)
            {
                if (serviceContexts == null)
                    serviceContexts = new Dictionary<uint, byte[]>();
                uint contextId = ms.ReadUInt32BE();
                ushort contextDataLength = ms.ReadUInt16BE();
                byte[] contextData = ms.ReadBytes(contextDataLength);
                serviceContexts.Add(contextId, contextData);
            }

            uint messageBodyLength = ms.ReadUInt32BE();
            ushort bindingsCount = ms.ReadUInt16BE();

            for (ushort i = 0; i < bindingsCount; i++)
            {
                Name name = new Name(ms);
                byte bindingType = ms.ReadUInt8();
                InteroperableObjectReference ior = new InteroperableObjectReference(ms);

                bool profileProcessed = ior.TaggedProfiles.Length == 0;

                foreach (TaggedProfile taggedProfile in ior.TaggedProfiles)
                {
                    switch (taggedProfile.ProfileIdTag)
                    {
                        case 0x49534f06:    //TAG_BIOP (BIOP Profile Body)
                            BiopProfileBody biopProfileBody = new BiopProfileBody(taggedProfile.ProfileData);
                            KeyValuePair<string, string> keyValuePair = name.NameComponents.First();
                            string valueType = keyValuePair.Value.ToLowerInvariant();
                            switch (valueType)
                            {
                                case "fil":
                                    VfsFile vfsFile = new VfsFile(vfs.SourcePid, vfs.ProgramMapping);
                                    vfsFile.Name = keyValuePair.Key;
                                    vfsFile.ParentDirectory = result;
                                    vfsFile.ObjectLocation = biopProfileBody.ObjectLocation;
                                    vfs.vfsFiles.Add(vfsFile);
                                    result.files.Add(vfsFile);
                                    profileProcessed = true;
                                    break;
                                case "dir":
                                    VfsDirectory vfsDirectory = new VfsDirectory();
                                    vfsDirectory.Name = keyValuePair.Key;
                                    vfsDirectory.ParentDirectory = result;
                                    vfsDirectory.ObjectLocation = biopProfileBody.ObjectLocation;
                                    vfs.vfsDirectories.Add(vfsDirectory);
                                    result.subdirectories.Add(vfsDirectory);
                                    profileProcessed = true;
                                    break;
                                case "ste":
                                    VfsEvent vfsEvent = new VfsEvent();
                                    vfsEvent.Name = keyValuePair.Key;
                                    vfsEvent.ParentDirectory = result;
                                    vfsEvent.ObjectLocation = biopProfileBody.ObjectLocation;
                                    vfs.vfsEvents.Add(vfsEvent);
                                    result.events.Add(vfsEvent);
                                    profileProcessed = true;
                                    break;
                                default:
                                    throw new NotImplementedException(keyValuePair.Value);
                            }
                            break;
                        default:
                            throw new NotImplementedException(String.Format("{0:X8}<e", taggedProfile.ProfileIdTag));
                    }
                }

                ushort objectInfo2Length = ms.ReadUInt16BE();
                byte[] objectInfo2Data = ms.ReadBytes(objectInfo2Length);
            }

            return result;
        }

        private static void DecodeStreamEvent(MemoryStream ms, ModuleInfo module, byte[] objectKeyData, ObjectCarouselMetadata vfs)
        {
            ushort objectInfoLength = ms.ReadUInt16BE();
            ushort n6 = objectInfoLength;
            Info_T infoT = new Info_T(ms);
            EventList_T eventListT = new EventList_T(ms);
            int iEnd = n6 - (infoT.N2 + 10) - (2 + eventListT.N3 + eventListT.N4);
            if (iEnd < 0)
                return;
            byte[] objectInfo = ms.ReadBytes(iEnd);
            byte serviceContextListCount = ms.ReadUInt8();

            Dictionary<uint, string> context = new Dictionary<uint, string>();
            for (int i = 0; i < eventListT.N3; i++)
            {
                uint contextId = ms.ReadUInt32BE();
                ushort contextDataLength = ms.ReadUInt16BE();
                if (contextDataLength > ms.GetAvailableBytes())
                    return;
                string contextData = Encoding.UTF8.GetString(ms.ReadBytes(contextDataLength));
                context.Add(contextId, contextData);
            }

            uint messageBodyLength = ms.ReadUInt32BE();
            byte tapsCount = ms.ReadUInt8();
            Tap[] taps = new Tap[tapsCount];
            for (int i = 0; i < tapsCount; i++)
            {
                taps[i] = new Tap(ms);
            }

            byte eventIdsCount = ms.ReadUInt8();
            ushort[] eventIds = new ushort[eventIdsCount];
            for (int i = 0; i < eventIdsCount; i++)
                eventIds[i] = ms.ReadUInt16BE();

            for (int i = 0; i < tapsCount; i++)
            {
                TapUseDefinitions tud = (TapUseDefinitions)taps[i].Use;
                switch (tud)
                {
                    case TapUseDefinitions.STR_EVENT_USE:
                        vfs.EventHandler.NotifySubStream(vfs.ProgramMapping, taps[i], eventIds, context, eventListT, infoT);
                        vfs.DirectoriesCreated++;
                        break;
                    default:
                        throw new NotImplementedException(String.Format("{0:X4}", tud));
                }
            }
        }
    }
}
