using Obfuz.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using UnityEngine;

namespace Obfuz.EncryptionVM
{
    public class VirtualMachineCodeGenerator
    {
        private readonly int _opCodeCount;
        private readonly int _opCodeBits;
        private readonly VirtualMachine _vm;

        public VirtualMachineCodeGenerator(string vmCodeGenerateSecretKey, int opCodeCount)
        {
            _opCodeCount = opCodeCount;
            _opCodeBits = EncryptionUtil.GetBitCount(opCodeCount - 1);
            _vm = new VirtualMachineCreator(vmCodeGenerateSecretKey).CreateVirtualMachine(opCodeCount);
        }

        public VirtualMachineCodeGenerator(VirtualMachine vm)
        {
            _opCodeCount = vm.opCodes.Length;
            _opCodeBits = EncryptionUtil.GetBitCount(_opCodeCount - 1);
            _vm = vm;
        }


        public bool ValidateMatch(string outputFile)
        {
            if (!File.Exists(outputFile))
            {
                return false;
            }
            string oldCode = NormalizeText(File.ReadAllText(outputFile, Encoding.UTF8));
            string newCode = NormalizeText(GenerateCode());
            return oldCode == newCode;
        }

        private static string NormalizeText(string input)
        {
            return Regex.Replace(input, @"\s+", string.Empty);
        }

        public void Generate(string outputFile)
        {
            FileUtil.CreateParentDir(outputFile);

            string code = GenerateCode();

            File.WriteAllText(outputFile, code, Encoding.UTF8);
            Debug.Log($"Generate EncryptionVM code to {outputFile}");
            UnityEditor.AssetDatabase.Refresh();
        }

        private string GenerateCode()
        {
            var lines = new List<string>();
            AppendHeader(lines);
            AppendEncryptCodes(lines);
            AppendDecryptCodes(lines);
            AppendTailer(lines);
            return string.Join("\n", lines);
        }

        private void AppendEncryptCodes(List<string> lines)
        {
            lines.Add(@"
        private int ExecuteEncrypt(int value, int opCode, int salt)
        {
            switch (opCode)
            {");
            foreach (var opCode in _vm.opCodes)
            {
                lines.Add($@"            case {opCode.code}:
            {{
                // {opCode.function.GetType().Name}");
                AppendEncryptCode(lines, opCode.function);
                lines.Add(@"                return value;
            }");
            }

            lines.Add(@"
                default:
                    throw new System.Exception($""Invalid opCode:{opCode}"");
            }
        }");
        }

        private void AppendDecryptCodes(List<string> lines)
        {
            lines.Add(@"
        private int ExecuteDecrypt(int value, int opCode, int salt)
        {
            switch (opCode)
            {");
            foreach (var opCode in _vm.opCodes)
            {
                lines.Add($@"            case {opCode.code}:
            {{
                // {opCode.function.GetType().Name}");
                AppendDecryptCode(lines, opCode.function);
                lines.Add(@"                return value;
            }");
            }

            lines.Add(@"
                default:
                    throw new System.Exception($""Invalid opCode:{opCode}"");
            }
        }");
        }

        private void AppendHeader(List<string> lines)
        {

            lines.Add($"/// This file is auto-generated by Obfuz. Do not modify it.");
            lines.Add($"///");
            //lines.Add($"/// Created Time: {DateTime.Now}");

            lines.Add($"/// Version: {_vm.version}");
            lines.Add($"/// SecretKey: {_vm.codeGenerationSecretKey}");
            lines.Add($"/// OpCodeCount: {_vm.opCodes.Length}");

            lines.Add(@"
namespace Obfuz.EncryptionVM
{
    public class GeneratedEncryptionVirtualMachine : Obfuz.EncryptorBase
    {");
            lines.Add($@"
        private const int kOpCodeBits = {_opCodeBits};

        private const int kOpCodeCount = {_opCodeCount};

        private const int kOpCodeMask = {_opCodeCount - 1};
");
            lines.Add(@"

        private readonly int[] _secretKey;

        public GeneratedEncryptionVirtualMachine(byte[] secretKey)
        {
            this._secretKey = ConvertToIntKey(secretKey);
        }

        public override int OpCodeCount => kOpCodeCount;

        public override int Encrypt(int value, int opts, int salt)
        {
            uint uopts = (uint)opts;
            uint revertOps = 0;
            while (uopts != 0)
            {
                uint opCode = uopts & kOpCodeMask;
                revertOps <<= kOpCodeBits;
                revertOps |= opCode;
                uopts >>= kOpCodeBits;
            }

            while (revertOps != 0)
            {
                uint opCode = revertOps & kOpCodeMask;
                value = ExecuteEncrypt(value, (int)opCode, salt);
                revertOps >>= kOpCodeBits;
            }
            return value;
        }

        public override int Decrypt(int value, int opts, int salt)
        {
            uint uopts = (uint)opts;
            while (uopts != 0)
            {
                uint opCode = uopts & kOpCodeMask;
                value = ExecuteDecrypt(value, (int)opCode, salt);
                uopts >>= kOpCodeBits;
            }
            return value;
        }
");
        }

        private void AppendTailer(List<string> lines)
        {
            lines.Add(@"
    }
}

");
        }

        private void AppendEncryptCode(List<string> lines, IEncryptionInstruction instruction)
        {
            instruction.GenerateEncryptCode(lines, "                ");
        }

        private void AppendDecryptCode(List<string> lines, IEncryptionInstruction instruction)
        {
            instruction.GenerateDecryptCode(lines, "                ");
        }
    }
}
