using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net.Configuration;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using McCrypt;
using McCrypt.PackData;

namespace McDecryptor
{
    class Program
    {
        public struct ContentListing
        {
            public string Path;
            public string Type;
            public string Name;
            public int Id;
        }
        public static string EscapeFilename(string filename)
        {
            return filename.Replace("/", "_").Replace("\\", "_").Replace(":", "_").Replace("?", "_").Replace("*", "_").Replace("<", "_").Replace(">", "_").Replace("|", "_").Replace("\"", "_");
        }

        static void CopyFile(string src, string dst)
        {
            using (FileStream fs = File.OpenRead(src))
            {
                using(FileStream wfd = File.OpenWrite(dst))
                {
                    fs.CopyTo(wfd);
                }
            }
        }

        static void CopyDirectory(string sourcePath, string targetPath)
        {
            List<Thread> threads = new List<Thread>();

            //Now Create all of the directories
            foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
            {
                Thread thrd = new Thread(() =>
                {
                    Directory.CreateDirectory(dirPath.Replace(sourcePath, targetPath));
                });
                thrd.Priority = ThreadPriority.Highest;
                thrd.Start();
                if (!Config.MultiThread) thrd.Join();
                else threads.Add(thrd);
            }

            foreach (Thread t in threads.ToArray())
                t.Join();
            threads.Clear();

            //Copy all the files & Replaces any files with the same name
            foreach (string newPath in Directory.GetFiles(sourcePath, "*", SearchOption.AllDirectories))
            {

                Thread thrd = new Thread(() =>
                {
                    CopyFile(newPath, newPath.Replace(sourcePath, targetPath));
                });
                thrd.Priority = ThreadPriority.Highest;
                thrd.Start();
                if (!Config.MultiThread) thrd.Join();
                else threads.Add(thrd);

            }

            foreach (Thread t in threads.ToArray())
                t.Join();

            threads.Clear();
        }

        static void DecryptPack(string filePath)
        {
            string levelDatFile = Path.Combine(filePath, "level.dat");
            string skinsJsonFile = Path.Combine(filePath, "skins.json");
            string oldSchoolZipe = Path.Combine(filePath, "content.zipe");

            Marketplace.DecryptContents(filePath);

            if (Config.CrackPacks)
            {
                if (File.Exists(oldSchoolZipe))
                    Marketplace.CrackZipe(oldSchoolZipe);

                if (File.Exists(levelDatFile))
                    Marketplace.CrackLevelDat(levelDatFile);

                if (File.Exists(skinsJsonFile))
                    Marketplace.CrackSkinsJson(skinsJsonFile);
            }
        }


        static void Main(string[] args)
        {
            Console.WriteLine("-- McDecryptor --");

            Console.WriteLine("Reading Configuration File...");
            Config.Init();
            Directory.SetCurrentDirectory(Config.ApplicationDirectory);
            Config.ReadConfig("McDecryptor.cfg");
            
            Keys.KeyDbFile = Config.KeysDbPath;
            if (File.Exists(Config.KeysDbPath))
            {
                Console.WriteLine("Parsing Key Database File...");
                Keys.ReadKeysDb(Config.KeysDbPath);
            }


            if (File.Exists(Config.OptionsTxt))
            {
                Keys.ReadOptionsTxt(Config.OptionsTxt);
            }

            Console.WriteLine("Minecraft Folder: " + Config.MinecraftFolder);


            if (Directory.Exists(Config.MinecraftFolder))
            {
                string[] entFiles = Directory.GetFiles(Config.LocalState, "*.ent", SearchOption.TopDirectoryOnly);

                foreach (string entFile in entFiles)
                {
                    Console.WriteLine("Reading Entitlement File: " + Path.GetFileName(entFile));
                    Keys.ReadEntitlementFile(entFile);
                }
            }


            string[] entFilesWorkDir = Directory.GetFiles(Config.ApplicationDirectory, "*.ent", SearchOption.TopDirectoryOnly);

            foreach (string entFile in entFilesWorkDir)
            {
                Console.WriteLine("Reading Entitlement File: " + Path.GetFileName(entFile));
                Keys.ReadEntitlementFile(entFile);
            }

            
            Console.WriteLine("\n\n");
            Console.WriteLine("Select what to decrypt: ");
            int total = 1;

            PReader pReader = new PReader();
            PEntry[] entries = pReader.PEntryList;

            foreach (PEntry entry in entries)
            {
                Console.WriteLine(total.ToString() + ") (" + entry.ProductType + ") " + entry.Name);

                total++;
            }

            Console.WriteLine("Select one or multiple (seperated by ',') or write \"ALL\", numerical values");

            List<int> toDecrypt = new List<int>();
            while (true)
            {
                Console.Write("Which do you want to decrypt? ");
                try
                {
                    string readText = Console.ReadLine();
                    if (readText.ToUpper() == "ALL")
                    {
                        for(int i = 0; i < total-1; i++)
                            toDecrypt.Add(i);
                        break;
                    }
                    string[] txtentries = readText.Split(',');

                    foreach(string entry in txtentries) {
                        int tdc = Convert.ToInt32(entry.Trim())-1;
                        if (tdc < 0 || tdc >= total)
                            continue;
                        toDecrypt.Add(tdc);
                    }

                    break;
                }
                catch (Exception e) { Console.WriteLine("Error: " + e.Message); }
            }

            

            foreach (int decryptMe in toDecrypt.ToArray())
            {
                PEntry cEntry = entries[decryptMe];
                string outFolder = Path.Combine(Config.OutFolder, cEntry.ProductType, EscapeFilename(cEntry.Name));

                int counter = 1;
                string ogOutFolder = outFolder;
                while (Directory.Exists(outFolder))
                {
                    outFolder = ogOutFolder + "_" + counter.ToString();
                    counter++;
                }

                Console.WriteLine("Decrypting: " + cEntry.Name);
                Directory.CreateDirectory(outFolder);
                try
                {
                    if (cEntry.ProductType == "addon")
                    {
                        // copy & decrypt main file
                        string subDir = Path.Combine(outFolder, cEntry.Type);
                        Directory.CreateDirectory(subDir);
                        CopyDirectory(cEntry.FilePath, subDir);
                        DecryptPack(subDir);

                        // copy & decrypt file dependancies
                        PEntry[] deps = pReader.GetDependancies(cEntry);
                        foreach (PEntry dependancy in deps)
                        {
                            string newDir = Path.Combine(outFolder, dependancy.Type);
                            Console.WriteLine("Decrypting dependancy: " + dependancy.Type + "/" + dependancy.Name);
                            Directory.CreateDirectory(newDir);

                            CopyDirectory(dependancy.FilePath, newDir);
                            DecryptPack(newDir);
                        }

                    }
                    else if(cEntry.ProductType == "minecraftWorlds")
                    {
                        CopyDirectory(cEntry.FilePath, outFolder);
                        DecryptPack(outFolder);

                        PEntry[] deps = pReader.GetDependancies(cEntry);
                        foreach (PEntry dependancy in deps)
                        {
                            Console.WriteLine("Decrypting dependancy: " + dependancy.ProductType+"/"+dependancy.Name);
                            string newDir = Path.Combine(outFolder, dependancy.ProductType, Path.GetFileName(dependancy.FilePath));

                            CopyDirectory(dependancy.FilePath, newDir);
                            DecryptPack(newDir);
                        }
                    }
                    else
                    {
                        CopyDirectory(cEntry.FilePath, outFolder);
                        DecryptPack(outFolder);
                    }

                    if (Config.ZipPacks)
                    {
                        Console.WriteLine("Zipping: " + cEntry.Name);

                        string ext = "";
                        if (cEntry.ProductType == "world_templates")
                            ext += ".mctemplate";
                        else if (cEntry.ProductType == "minecraftWorlds")
                            ext += ".mcworld";
                        else if (cEntry.ProductType == "addon")
                            ext += ".mcaddon";
                        else
                            ext += ".mcpack";

                        string fname = outFolder + ext;

                        if (File.Exists(fname))
                            File.Delete(fname);

                        ZipFile.CreateFromDirectory(outFolder, fname, CompressionLevel.NoCompression, false);
                        Directory.Delete(outFolder, true);
                    }
                }
                catch (Exception e)
                {
                    Console.Error.WriteLine("Failed to decrypt: " + cEntry.Name + " " + e.Message);
                    Directory.Delete(outFolder, true);
                }
                


            }

            Console.WriteLine("Finished.");
            Console.ReadKey();
        }

    }
}
