using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;

namespace RMDEC
{
    public class MVProject
    {

        public MVProject() { }

        //Private internal variables
        private byte[] encryptionKey = new byte[0x10];

        private bool isKeySet = false;
        private bool encryptedImages = false;
        private bool encryptedAudio = false;
        public bool MZ = false;
        private string gameTitle;
        private string filePath;
        private string systemJsonFile;
        private dynamic jsonData;

        //Public readable variables
        public Boolean IsEncrypted
        {
            get
            {
                if (EncryptedAudio == true || EncryptedImages == true)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public Byte[] EncryptionKey
        {
            get
            {
                if(isKeySet)
                {
                    return encryptionKey;
                }
                else
                {
                    genEncryptionKey();
                    return encryptionKey;
                }
                
            }
        }
        public String GameTitle
        {
            get
            {
                return gameTitle;
            }
        }
        public Boolean EncryptedImages
        {
            get
            {
                return encryptedImages;
            }
            set
            {
                if(jsonData != null)
                {
                    jsonData.hasEncryptedImages = value;
                    File.WriteAllText(systemJsonFile, jsonData.ToString(Formatting.None));
                }
                encryptedImages = value;
            }
        }

        public Boolean EncryptedAudio
        {
            get
            {
                return encryptedAudio;
            }
            set
            {
                if (jsonData != null)
                {
                    jsonData.hasEncryptedAudio = value;
                    File.WriteAllText(systemJsonFile, jsonData.ToString(Formatting.None));
                }
                encryptedAudio = value;
            }
        }

        public String FilePath
        {
            get
            {
                return filePath;
            }
        }

        //Private functions
        private static byte[] hexStr2Bytes(String HexStr)
        {
            if (HexStr.Length % 2 != 0)
            {
                throw new InvalidDataException(HexStr + " Is not divisible by 2!");
            }

            List<byte> byteList = new List<byte>();
            
            for (int i = 0; i < HexStr.Length; i += 2)
            {
                string curHex = HexStr.Substring(i, 2);
                byte hexByte = Byte.Parse(curHex, NumberStyles.HexNumber);

                byteList.Add(hexByte);
            }

            byte[] resultingByteArray = byteList.ToArray();
            return resultingByteArray;

        }

        private static byte[] guessKey(string path) // Incase you think your smart
        {
            string[] mzPngs = Directory.GetFiles(path, "*._png", SearchOption.AllDirectories);
            string[] mvPngs = Directory.GetFiles(path, "*.rpgmvp", SearchOption.AllDirectories);
            string[] mzM4as = Directory.GetFiles(path, "*._m4a", SearchOption.AllDirectories);
            string[] mvM4as = Directory.GetFiles(path, "*.rpgmvm", SearchOption.AllDirectories);

            List<string> files = new List<string>();
            files.AddRange(mzPngs);
            files.AddRange(mvPngs);

            files.AddRange(mzM4as);
            files.AddRange(mvM4as);

            if (files.Count <= 0)
                return new byte[0x10];

            Random rng = new Random();
            int index = rng.Next(0, files.Count);
            string file = files[index];

            byte[] encryptedHeader = new byte[0x10];
            FileStream fs = File.OpenRead(file);
            fs.Seek(0x10, SeekOrigin.Begin);
            fs.Read(encryptedHeader, 0x00, 0x10);
            fs.Close();

            string filetype = Path.GetExtension(file).ToLower();
            byte[] pngHeader = new byte[0x10] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52 };
            byte[] m4aHeader = new byte[0x10] { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20, 0x00, 0x00, 0x00, 0x00 };

            if (filetype == "._png" || filetype == ".rpgmvp") {
                byte[] key = RMProject.Xor(encryptedHeader, pngHeader);
                if(!tryKey(path, key))
                {
                    throw new Exception("Cannot find key (ITS TOO STRONG!)");
                }
                else
                {
                    return key;
                }
            }
            else if (filetype == "._m4a" || filetype == ".rpgmvm")
            {
                byte[] key = RMProject.Xor(encryptedHeader, m4aHeader);
                if (!tryKey(path, key))
                {
                    throw new Exception("Cannot find key (ITS TOO STRONG!)");
                }
                else
                {
                    return key;
                }
            }
            // OGG Header is not 0x10 bytes unfortunately.

            throw new Exception("Cannot find key (ITS TOO STRONG!)");
        }
        private static bool tryKey(string path, byte[] key) // Checks if the key really works
        {
            string[] mzPngs = Directory.GetFiles(path, "*._png", SearchOption.AllDirectories);
            string[] mvPngs = Directory.GetFiles(path, "*.rpgmvp", SearchOption.AllDirectories);
            string[] mzM4as = Directory.GetFiles(path, "*._m4a", SearchOption.AllDirectories);
            string[] mvM4as = Directory.GetFiles(path, "*.rpgmvm", SearchOption.AllDirectories);
            string[] mzOggs = Directory.GetFiles(path, "*._ogg", SearchOption.AllDirectories);
            string[] mvOggs = Directory.GetFiles(path, "*.rpgmvo", SearchOption.AllDirectories);

            List<string> files = new List<string>();
            files.AddRange(mzPngs);
            files.AddRange(mvPngs);

            files.AddRange(mzM4as);
            files.AddRange(mvM4as);

            files.AddRange(mzOggs);
            files.AddRange(mvOggs);

            if (files.Count <= 0)
                return false;

            Random rng = new Random();
            int index = rng.Next(0, files.Count);
            string file = files[index];

            byte[] encryptedHeader = new byte[0x10];
            FileStream fs = File.OpenRead(file);
            fs.Seek(0x10, SeekOrigin.Begin);
            fs.Read(encryptedHeader, 0x00, 0x10);
            fs.Close();

            byte[] plaintextHeader = RMProject.Xor(encryptedHeader, key);

            string filetype = Path.GetExtension(file).ToLower();
            if (filetype == "._png" || filetype == ".rpgmvp")
                if (Encoding.ASCII.GetString(plaintextHeader).Contains("PNG"))
                    return true;
                else
                    return false;
            else if (filetype == "._ogg" || filetype == ".rpgmvo")
                if (Encoding.ASCII.GetString(plaintextHeader).StartsWith("OggS"))
                    return true;
                else
                    return false;
            else if (filetype == "._m4a" || filetype == ".rpgmvm")
                if (Encoding.ASCII.GetString(plaintextHeader).Contains("ftypM4A"))
                    return true;
                else
                    return false;
            else // No encrypted files?
                return false;
        }

        private static MVProject GuessProject(string folder) 
        {
            MVProject mvp = new MVProject();
            
            mvp.MZ = false;
            mvp.encryptedImages = false;
            mvp.encryptedAudio = false;
            folder = Path.GetDirectoryName(Path.GetDirectoryName(folder));
            string[] mzPngs = Directory.GetFiles(folder, "*._png", SearchOption.AllDirectories);
            string[] mvPngs = Directory.GetFiles(folder, "*.rpgmvp", SearchOption.AllDirectories);
            string[] mzM4as = Directory.GetFiles(folder, "*._m4a", SearchOption.AllDirectories);
            string[] mvM4as = Directory.GetFiles(folder, "*.rpgmvm", SearchOption.AllDirectories);
            string[] mzOggs = Directory.GetFiles(folder, "*._ogg", SearchOption.AllDirectories);
            string[] mvOggs = Directory.GetFiles(folder, "*.rpgmvo", SearchOption.AllDirectories);

            string[] mzArtifacts = Directory.GetFiles(folder, "rmmz_*", SearchOption.AllDirectories);
            string[] mvArtifacts = Directory.GetFiles(folder, "rpg_*", SearchOption.AllDirectories);

            // Look for PNG
            if (mvPngs.Length > 0)
            {
                mvp.MZ = false;
                mvp.encryptedImages = true;
            }
            else if (mzPngs.Length > 0)
            {
                mvp.MZ = true;
                mvp.encryptedImages = true;
            }
            
            // Look for M4A
            if(mvM4as.Length > 0)
            {
                mvp.MZ = false;
                mvp.encryptedAudio = true;
            }
            else if (mzM4as.Length > 0)
            {
                mvp.MZ = true;
                mvp.encryptedAudio = true;
            }

            // Look for OGG
            if (mvOggs.Length > 0)
            {
                mvp.MZ = false;
                mvp.encryptedAudio = true;
            }
            else if (mzOggs.Length > 0)
            {
                mvp.MZ = true;
                mvp.encryptedAudio = true;
            }

            // Determine MV or MZ
            if(mzArtifacts.Length > 0)
            {
                mvp.MZ = true;
                mvp.filePath = Path.GetDirectoryName(Path.GetDirectoryName(mzArtifacts[0]));
            }
            else if(mvArtifacts.Length > 0)
            {
                mvp.MZ = false;
                mvp.filePath = Path.GetDirectoryName(Path.GetDirectoryName(mvArtifacts[0]));
            }

            if(mvp.IsEncrypted)
            {
                byte[] key = guessKey(folder);
                mvp.encryptionKey = key;
                mvp.isKeySet = true;
            }

            mvp.systemJsonFile = null;
            mvp.jsonData = null;

            return mvp;
        }

        private void genEncryptionKey()
        {
            byte[] keyData = new byte[0x10];
            Random rng = new Random((int)DateTime.Now.Ticks);
            rng.NextBytes(keyData);
            encryptionKey = keyData;
            jsonData.encryptionKey = BitConverter.ToString(keyData, 0x00, keyData.Length).Replace("-", "");
            File.WriteAllText(systemJsonFile, jsonData.ToString(Formatting.None));
            isKeySet = true;
        }

        //Public functions
        public static MVProject ParseSystemJson(string path)
        {

            //Check if valid system.json

            if (File.Exists(path) && Path.GetExtension(path).ToLower() == ".json")
            {

                MVProject mvp = new MVProject();

                string jsonStr = File.ReadAllText(path, Encoding.UTF8);
                dynamic systemJson = JObject.Parse(jsonStr);


                if (systemJson.gameTitle != null)
                {
                    mvp.gameTitle = systemJson.gameTitle;
                }
                else
                {
                    return GuessProject(path);
                }

                if (systemJson.hasEncryptedAudio != null)
                {
                    mvp.encryptedAudio = systemJson.hasEncryptedAudio;
                }

                if (systemJson.hasEncryptedImages != null)
                {
                    mvp.encryptedImages = systemJson.hasEncryptedImages;
                }

                if (systemJson.encryptionKey != null)
                {
                    string encKey = systemJson.encryptionKey;
                    byte[] key = hexStr2Bytes(encKey);
                    if(tryKey(Path.GetDirectoryName(Path.GetDirectoryName(path)), key))
                    {
                        mvp.encryptionKey = key;
                        mvp.isKeySet = true;
                    }
                    else
                    {
                        key = guessKey(Path.GetDirectoryName(Path.GetDirectoryName(path)));
                        mvp.encryptionKey = key;
                        mvp.isKeySet = true;
                    }
                }

                mvp.filePath = Path.GetDirectoryName(Path.GetDirectoryName(path));

                // Detect MV or MZ

                if (File.Exists(Path.Combine(mvp.filePath, "js", "rmmz_core.js")))
                {
                    mvp.MZ = true;
                }
                else
                {
                    mvp.MZ = false;
                }

                mvp.systemJsonFile = path;
                mvp.jsonData = systemJson;


                return mvp;
            }
            else
            {
                return GuessProject(path);
            }
        }

        public void EncryptFile(Stream inStream, Stream outStream, byte[] key = null)
        {
            if (key == null)
                key = EncryptionKey;

            outStream.Seek(0x00, SeekOrigin.Begin);
            outStream.SetLength(0x00);

            BinaryWriter boutStream = new BinaryWriter(outStream);
            string magic = "RPGMV";
            byte[] magicBytes = Encoding.UTF8.GetBytes(magic);

            outStream.Write(magicBytes, 0x00, magicBytes.Length);
            boutStream.Write(0x00);
            boutStream.Write(0x103);
            outStream.Write(new byte[0x3], 0x00, 0x3);

            inStream.Seek(0x00, SeekOrigin.Begin);
            byte[] plaintextHeader = new byte[0x10];
            inStream.Read(plaintextHeader, 0x00, 0x10);

            byte[] encryptedHeader = RMProject.Xor(plaintextHeader, key);
            outStream.Write(encryptedHeader, 0x00, encryptedHeader.Length);
            inStream.CopyTo(outStream);
        }
        public void DecryptFile(Stream inStream, Stream outStream, byte[] key=null)
        {
            if (key == null)
                key = EncryptionKey;

            inStream.Seek(0x00, SeekOrigin.Begin);

            byte[] magic = new byte[0x05];
            inStream.Read(magic, 0x00,0x05);
            string magicStr = Encoding.UTF8.GetString(magic);

            if(magicStr != "RPGMV")
            {
                throw new InvalidDataException("Not an encrypted file!");
            }

            inStream.Seek(0x10, SeekOrigin.Begin);
            byte[] encryptedHeader = new byte[0x10];
            inStream.Read(encryptedHeader, 0x00, 0x10);

            byte[] plaintextHeader = RMProject.Xor(encryptedHeader, key);

            outStream.Seek(0x00, SeekOrigin.Begin);
            outStream.SetLength(0);

            outStream.Write(plaintextHeader, 0x00, plaintextHeader.Length);
            inStream.CopyTo(outStream);
        }

    }
}
