using System;
using System.Collections.Generic;
using System.IO;
using DotNetCTFDumper.MMFParser.EXE.Loaders;
using DotNetCTFDumper.MMFParser.EXE.Loaders.Banks;
using DotNetCTFDumper.MMFParser.EXE.Loaders.Events;
using DotNetCTFDumper.Utils;

namespace DotNetCTFDumper.MMFParser.EXE
{
    public class ChunkList
    {
        public List<Chunk> Chunks = new List<Chunk>();
        public bool Verbose = false;
        public List<Frame> Frames = new List<Frame>();

        public void Read(ByteReader reader)
        {
            Chunks.Clear();
            while (true)
            {
                Chunk chunk = new Chunk(Chunks.Count, this);
                chunk.Verbose = Verbose;
                chunk.Read(reader);
                chunk.Loader = LoadChunk(chunk);
                Chunks.Add(chunk);
                if (chunk.Id == 8750) chunk.BuildKey();
                if (reader.Tell() >= reader.Size()) break;
                if (chunk.Id == 32639) break; //LAST chunkID
            }
        }

        public class Chunk
        {
            ChunkList _chunkList;
            public string Name = "UNKNOWN";
            int _uid;
            public int Id = 0;
            
            public ChunkLoader Loader;
            public byte[] ChunkData;
            public byte[] RawData;
            public ChunkFlags Flag;
            public int Size;
            public int DecompressedSize = -1;
            public bool Verbose = false;

            public Chunk(int actualuid, ChunkList actualChunkList)
            {
                _uid = actualuid;
                _chunkList = actualChunkList;
            }

            public ByteReader GetReader()
            {
                return new ByteReader(ChunkData);
            }

            public void Read(ByteReader exeReader)
            {
                
                Id = exeReader.ReadInt16();
                Name = _chunkList.GetNameByID(Id);

                Flag = (ChunkFlags) exeReader.ReadInt16();
                Size = exeReader.ReadInt32();
                if((Id!=26214&&Id!=26216)) //To prevent RAM from commiting suicide
                {                
                    RawData = exeReader.ReadBytes(Size);
                    exeReader.BaseStream.Position -= Size;
                    //Saving raw data cuz why not 
                }

                switch (Flag)
                {
                    case ChunkFlags.Encrypted:                       
                        ChunkData = Decryption.DecodeChunk(exeReader.ReadBytes(Size),Size);
                        break;
                    case ChunkFlags.CompressedAndEncrypyed:
                        ChunkData = Decryption.DecodeMode3(exeReader.ReadBytes(Size), Size,Id,out DecompressedSize);
                        break;
                    case ChunkFlags.Compressed:
                        ChunkData = Decompressor.Decompress(exeReader,out DecompressedSize);
                        break;
                    case ChunkFlags.NotCompressed:
                        ChunkData = exeReader.ReadBytes(Size);
                        break;
                }


            }

            public void Save()
            {
                if (ChunkData != null)
                {
                    string path = $"{Settings.ChunkPath}\\{Name}.chunk";
                    File.WriteAllBytes(path, ChunkData);
                }

            }
            
            public void Print(bool extented)
            {
                if(extented)
                {
                    Logger.Log($"Chunk: {Name} ({_uid})", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    ID: {Id} - 0x{Id.ToString("X")}", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    Flags: {Flag}", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    Loader: {(Loader != null ? Loader.GetType().Name : "Empty Loader")}", true,ConsoleColor.DarkCyan);
                    Logger.Log($"    Size: {Size} B", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    Decompressed Size: {DecompressedSize} B", true, ConsoleColor.DarkCyan);
                    Logger.Log("---------------------------------------------", true, ConsoleColor.DarkCyan);
                    

                }
                else
                {
                    Logger.Log($"Chunk: {Name} ({_uid})", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    ID: {Id} - 0x{Id.ToString("X")}", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    Decompressed Size: {DecompressedSize} B", true, ConsoleColor.DarkCyan);
                    Logger.Log($"    Flags: {Flag}", true, ConsoleColor.DarkCyan);
                    Logger.Log("---------------------------------------------", true, ConsoleColor.DarkCyan);
                }
                
            }
            public void BuildKey()
            {
                
                
                
                Settings.AppName=_chunkList.GetChunk<AppName>()?.Value??"";
                Settings.Copyright = _chunkList.GetChunk<Copyright>()?.Value??"";
                Settings.ProjectPath = _chunkList.GetChunk<EditorFilename>()?.Value??"";
               

                if (Exe.Instance.GameData.ProductBuild > 284)Decryption.MakeKey(Settings.AppName,Settings.Copyright,Settings.ProjectPath);
                else Decryption.MakeKey(Settings.ProjectPath, Settings.AppName, Settings.Copyright);



            }
        }

        public enum ChunkFlags
        {
            NotCompressed = 0,
            Compressed = 1,
            Encrypted = 2,
            CompressedAndEncrypyed = 3
        }

        public ChunkLoader LoadChunk(Chunk chunk)
        {
            ChunkLoader loader = null;
            switch (chunk.Id)
            {
                case 8739:
                    loader = new AppHeader(chunk);
                    break;
                case 8740:
                    loader = new AppName(chunk);
                    break;
                case 8741:
                    loader = new AppAuthor(chunk);
                    break;
                case 8743:
                    loader = new ExtPath(chunk);
                    break;
                case 8750:
                    loader = new EditorFilename(chunk);
                    break;
                case 8751:
                    loader = new TargetFilename(chunk);
                    break;
                case 8752:
                    loader = new AppDoc(chunk);
                    break;
                case 8745:
                    loader = new FrameItems(chunk);
                    break;
                case 8757:
                    loader = new AppIcon(chunk);
                    break;
                case 8762:
                    loader = new AboutText(chunk);
                    break;
                case 8763:
                    loader = new Copyright(chunk);
                    break;
                case 13123:
                    loader = new DemoFilePath(chunk);
                    break;
                case 13109:
                    loader = new FrameName(chunk);
                    break;
                case 13107:
                    loader = new Frame(chunk);
                    Frames.Add((Frame)loader);
                    break;
                case 13108:
                    loader = new FrameHeader(chunk);
                    break;
                case 13111:
                    loader = new FramePalette(chunk);
                    break;
                case 13112:
                    loader = new ObjectInstances(chunk);
                    break;
                case 13115:
                    loader = new Transition(chunk);
                    break;
                case 13116:
                    loader = new Transition(chunk);
                    break;
                case 13121:
                    loader = new Layers(chunk);
                    break;
                case 26214:
                    loader = new ImageBank(chunk);
                    break;
                case 26216:
                    loader = new SoundBank(chunk);
                    break;
                case 26217:
                    loader = new MusicBank(chunk);
                    break;
                case 17477:
                    loader = new ObjectName(chunk);
                    break;
                case 17476:
                    loader = new ObjectHeader(chunk);
                    break;
                case 17478:
                    loader = new ObjectProperties(chunk);
                    return loader;
                    break;
                case 8788:
                    //loader = new ObjectNames(chunk);
                    break;
                case 8754:
                    loader = new GlobalValues(chunk);
                    break;
                case 8755:
                    loader = new GlobalStrings(chunk);
                    break;
                case 13117:
                    // loader = new Events(chunk);//NOT WORKING
                    break;
                
                
            }

            if (loader != null) loader.Read();
            return loader;
        }


        public T GetChunk<T>() where T : ChunkLoader
        {
            foreach (Chunk chunk in Chunks)
            {
                if (chunk.Loader != null)
                {
                    if (chunk.Loader.GetType().Name == typeof(T).Name)
                    {
                        return (T) chunk.Loader;
                    }
                }
            }
            //Logger.Log($"ChunkLoader {typeof(T).Name} not found", true, ConsoleColor.Red);
            return null;  
        }
        public T PopChunk<T>() where T : ChunkLoader
        {
            for(int i=0;i<Chunks.Count;i++)
            {
                var chunk = Chunks[i];
                if (chunk.Loader != null)
                {
                    if (chunk.Loader.GetType().Name == typeof(T).Name)
                    {
                        Chunks.Remove(chunk);
                        return (T) chunk.Loader;
                    }

                    
                }
            }
            return null;  
        }

        public string GetNameByID(int id)
        {
            switch (id)
            {
                case 4386: return "PREVIEW";
                case 8738: return "Mini Header";
                case 8739: return "Header";
                case 8740: return "Title";
                case 8741: return "Author";
                case 8742: return "Menu";
                case 8743: return "Extra Path";
                case 8744: return "Extensions";
                case 8745: return "Object Bank";
                case 8746: return "Global Events";
                case 8747: return "Frame Handles";
                case 8748: return "Extra Data";
                case 8749: return "Additional Extensions";
                case 8750: return "Project Path";
                case 8751: return "Output Path";
                case 8752: return "App Doc";
                case 8753: return "Other Extensions";
                case 8754: return "Global Values";
                case 8755: return "Global Strings";
                case 8756: return "Extensions List";
                case 8757: return "Icon";
                case 8758: return "Demo Version";
                case 8759: return "Security Number";
                case 8760: return "Binary Files";
                case 8761: return "Menu Images";
                case 8762: return "About";
                case 8763: return "Copyright";
                case 8764: return "Global Value Names";
                case 8765: return "Global String Names";
                case 8766: return "Movement Extensions";
                case 8767: return "Object Bank 2";
                case 8768: return "EXE Only";
                case 8770: return "Protection";
                case 8771: return "Shaders";
                case 8773: return "Extended Header";
                case 13107:return "Frame";
                case 13108:return "Frame Header";
                case 13109:return "Frame Name";
                case 13110:return "Frame Password";
                case 13111:return "Frame Palette";
                case 13112:return "Frame Objects";
                case 13113:return "Frame Fade In Frame";
                case 13114:return "Frame Fade Out Frame";
                case 13115:return "Frame Fade In";
                case 13116:return "Frame Fade Out";
                case 13117:return "Frame Events";
                case 13118:return "Frame Play Header";
                case 13119:return "Additional Frame Item";
                case 13120:return "Additional Object Instance";
                case 13121:return "Frame Layers";
                case 13122:return "Frame Virtual Rect";
                case 13123:return "Demo File Path";
                case 13124:return "Random Seed";
                case 13125:return "Frame Layer Effects";
                case 13126:return "Bluray Options";
                case 13127:return "MVTimer Base";
                case 13128:return "Mosaic Image Table";
                case 13129:return "Frame Effects";
                case 13130:return "Frame Iphone Options";
                case 17476:return "Object Header";
                case 17477:return "Object Name";
                case 17478:return "Object Common";
                case 17479:return "Object Unknown";
                case 17480:return "Object Effects";
                case 21845:return "Image Offsets";
                case 21846:return "Font Offsets";
                case 21847:return "Sound Offsets";
                case 21848:return "Music Offsets";
                case 26214:return "Image Bank";
                case 26215:return "Font Bank";
                case 26216:return "Sound Bank";
                case 26217:return "Music Bank";
                case 32639:return "Last";
                default: return $"Unknown-{id}";
            }
        }
        
    }
}