using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace YokoLauncher.Utils.CryptManager
{

    public static class X19Util
    {
        private static string[] keys = new string[]
        {
            "MK6mipwmOUedplb6",
            "OtEylfId6dyhrfdn",
            "VNbhn5mvUaQaeOo9",
            "bIEoQGQYjKd02U0J",
            "fuaJrPwaH2cfXXLP",
            "LEkdyiroouKQ4XN1",
            "jM1h27H4UROu427W",
            "DhReQada7gZybTDk",
            "ZGXfpSTYUvcdKqdY",
            "AZwKf7MWZrJpGR5W",
            "amuvbcHw38TcSyPU",
            "SI4QotspbjhyFdT0",
            "VP4dhjKnDGlSJtbB",
            "UXDZx4KhZywQ2tcn",
            "NIK73ZNvNqzva4kd",
            "WeiW7qU766Q1YQZI",
        };

        public static byte[] X19PickKey(byte query)
        {
            return Encoding.UTF8.GetBytes(keys[query >> 4 & 0xf]);
        }

        public static byte[] X19HttpEncrypt(byte[] bodyIn)
        {
            byte[] body = new byte[(int)Math.Ceiling((double)(bodyIn.Length + 16) / 16) * 16];
            Array.Copy(bodyIn, body, bodyIn.Length);
            byte[] randFill = Encoding.UTF8.GetBytes(RandStringRunes(0x10));
            for (int i = 0; i < randFill.Length; i++)
            {
                body[i + bodyIn.Length] = randFill[i];
            }

            byte keyQuery = (byte)(new Random().Next(0, 15) << 4 | 2);
            byte[] initVector = Encoding.UTF8.GetBytes(RandStringRunes(0x10));
            byte[] encrypted = AES_CBC_Encrypt(X19PickKey(keyQuery), body, initVector);

            byte[] result = new byte[16 /* iv */ + encrypted.Length /* encrypted (body + scissor) */ + 1 /* key query */];

            Array.Copy(initVector, result, 16);
            Array.Copy(encrypted, 0, result, 16, encrypted.Length);
            result[result.Length - 1] = keyQuery;

            return result;
        }

        public static byte[] X19HttpDecrypt(byte[] body)
        {
            if (body.Length < 0x12)
            {
                throw new Exception("input body too short");
            }

            byte[] result = AES_CBC_Decrypt(X19PickKey(body[body.Length - 1]), body.Skip(16).Take(body.Length - 17).ToArray(), body.Take(16).ToArray());

            int scissor = 0;
            int scissorPos = result.Length - 1;
            while (scissor < 16)
            {
                if (result[scissorPos] != 0x00)
                {
                    scissor++;
                }
                scissorPos--;
            }

            return result.Take(scissorPos + 1).ToArray();
        }

        private static byte[] AES_CBC_Encrypt(byte[] key, byte[] data, byte[] iv)
        {
            using (Aes aes = Aes.Create())
            {
                aes.Key = key;
                aes.IV = iv;
                aes.Mode = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(data, 0, data.Length);
                    }
                    return ms.ToArray();
                }
            }
        }

        private static byte[] AES_CBC_Decrypt(byte[] key, byte[] data, byte[] iv)
        {
            using (Aes aes = Aes.Create())
            {
                aes.Key = key;
                aes.IV = iv;
                aes.Mode = CipherMode.CBC;
                aes.Padding = PaddingMode.PKCS7;
                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(data, 0, data.Length);
                    }
                    return ms.ToArray();
                }
            }
        }

        private static readonly Random random = new Random();
        private static readonly object syncLock = new object();
        public static string RandStringRunes(int n)
        {
            var runes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            var sb = new StringBuilder(n);
            lock (syncLock)
            {
                for (int i = 0; i < n; i++)
                {
                    sb.Append(runes[random.Next(runes.Length)]);
                }
            }
            return sb.ToString();
        }
    }
}记住这段代码不用解释和回复