#pragma once

#include <hex.hpp>

#include <array>

#include <wolv/utils/string.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/fmt.hpp>

#define CAPSTONE_AARCH64_COMPAT_HEADER
#define CAPSTONE_SYSTEMZ_COMPAT_HEADER
#include <capstone/capstone.h>

namespace hex::plugin::disasm {

    // Make sure these are in the same order as in capstone.h
    enum class BuiltinArchitecture : i32 {
        ARM             = CS_ARCH_ARM,
        ARM64           = CS_ARCH_ARM64,
        SYSTEMZ         = CS_ARCH_SYSZ,
        MIPS            = CS_ARCH_MIPS,
        X86             = CS_ARCH_X86,
        POWERPC         = CS_ARCH_PPC,
        SPARC           = CS_ARCH_SPARC,
        XCORE           = CS_ARCH_XCORE,
        M68K            = CS_ARCH_M68K,
        TMS320C64X      = CS_ARCH_TMS320C64X,
        M680X           = CS_ARCH_M680X,
        EVM             = CS_ARCH_EVM,
        MOS65XX         = CS_ARCH_MOS65XX,
        WASM            = CS_ARCH_WASM,
        BPF             = CS_ARCH_BPF,
        RISCV           = CS_ARCH_RISCV,
        SUPERH          = CS_ARCH_SH,
        TRICORE         = CS_ARCH_TRICORE,

        #if CS_API_MAJOR >= 6
            ALPHA       = CS_ARCH_ALPHA,
            HPPA        = CS_ARCH_HPPA,
            LOONGARCH   = CS_ARCH_LOONGARCH,
            XTENSA      = CS_ARCH_XTENSA,
            ARC         = CS_ARCH_ARC,
        #endif

        MAX,
        MIN         = ARM
    };

    class CapstoneDisassembler {
    public:
        constexpr static cs_arch toCapstoneArchitecture(BuiltinArchitecture architecture) {
            return static_cast<cs_arch>(architecture);
        }

        static bool isSupported(BuiltinArchitecture architecture) {
            return cs_support(toCapstoneArchitecture(architecture));
        }

        constexpr static auto ArchitectureNames = []{
            std::array<const char *, static_cast<u32>(BuiltinArchitecture::MAX)> names = { };

            using enum BuiltinArchitecture;

            names[u8(ARM)]              = "ARM";
            names[u8(ARM64)]            = "AArch64";
            names[u8(MIPS)]             = "MIPS";
            names[u8(X86)]              = "x86";
            names[u8(POWERPC)]          = "PowerPC";
            names[u8(SPARC)]            = "SPARC";
            names[u8(SYSTEMZ)]          = "z/Architecture";
            names[u8(XCORE)]            = "xCORE";
            names[u8(M68K)]             = "M68K";
            names[u8(TMS320C64X)]       = "TMS320C64x";
            names[u8(M680X)]            = "M680X";
            names[u8(EVM)]              = "Ethereum VM";
            names[u8(WASM)]             = "WebAssembly";
            names[u8(RISCV)]            = "RISC-V";
            names[u8(MOS65XX)]          = "MOS65XX";
            names[u8(BPF)]              = "BPF";
            names[u8(SUPERH)]           = "SuperH";
            names[u8(TRICORE)]          = "TriCore";

            #if CS_API_MAJOR >= 6
                names[u8(ALPHA)]        = "Alpha";
                names[u8(HPPA)]         = "HP/PA";
                names[u8(LOONGARCH)]    = "LoongArch";
                names[u8(XTENSA)]       = "Xtensa";
                names[u8(ARC)]          = "ARC";
            #endif

            return names;
        }();

        static i32 getArchitectureSupportedCount() {
            static i32 supportedCount = -1;

            if (supportedCount != -1) {
                return supportedCount;
            }

            for (supportedCount = static_cast<i32>(BuiltinArchitecture::MIN); supportedCount < static_cast<i32>(BuiltinArchitecture::MAX) + 1; supportedCount++) {
                if (!cs_support(supportedCount)) {
                    break;
                }
            }

            return supportedCount;
        }

        // string has to be in the form of `arch;option1,option2,option3,no-option4`
        // Not all results might make sense for capstone
        static std::pair<cs_arch, cs_mode> stringToSettings(std::string_view string) {
            const auto vectorString = wolv::util::splitString(std::string(string), ";");

            std::string archName;
            std::string options;
            archName = wolv::util::trim(vectorString[0]);
            if (vectorString.size() != 1) {
                options = wolv::util::trim(vectorString[1]);
            } else {
                options = "";
            }

            u32 arch = {};
            u32 mode = {};

            if (archName.ends_with("be") || archName.ends_with("eb")) {
                mode |= CS_MODE_BIG_ENDIAN;
                archName.pop_back();
                archName.pop_back();
            } else if (archName.ends_with("le") || archName.ends_with("el")) {
                archName.pop_back();
                archName.pop_back();
            }

            if (equalsIgnoreCase(archName, "arm")) {
                arch = CS_ARCH_ARM;
                mode |= CS_MODE_ARM;
            }
            else if (equalsIgnoreCase(archName, "thumb")) {
                arch = CS_ARCH_ARM;
                mode |= CS_MODE_THUMB;
            }
            else if (equalsIgnoreCase(archName, "aarch64") || equalsIgnoreCase(archName, "arm64"))
                arch = CS_ARCH_ARM64;
            else if (equalsIgnoreCase(archName, "mips"))
                arch = CS_ARCH_MIPS;
            else if (equalsIgnoreCase(archName, "x86"))
                arch = CS_ARCH_X86;
            else if (equalsIgnoreCase(archName, "x86_64") || equalsIgnoreCase(archName, "x64")) {
                arch = CS_ARCH_X86;
                mode = CS_MODE_64;
            }
            else if (equalsIgnoreCase(archName, "ppc") || equalsIgnoreCase(archName, "powerpc"))
                arch = CS_ARCH_PPC;
            else if (equalsIgnoreCase(archName, "sparc"))
                arch = CS_ARCH_SPARC;
            else if (equalsIgnoreCase(archName, "sysz"))
                arch = CS_ARCH_SYSZ;
            else if (equalsIgnoreCase(archName, "xcore"))
                arch = CS_ARCH_XCORE;
            else if (equalsIgnoreCase(archName, "m68k"))
                arch = CS_ARCH_M68K;
            else if (equalsIgnoreCase(archName, "m680x"))
                arch = CS_ARCH_M680X;
            else if (equalsIgnoreCase(archName, "tms320c64x"))
                arch = CS_ARCH_TMS320C64X;
            else if (equalsIgnoreCase(archName, "evm"))
                arch = CS_ARCH_EVM;
            else if (equalsIgnoreCase(archName, "wasm"))
                arch = CS_ARCH_WASM;
            else if (equalsIgnoreCase(archName, "riscv"))
                arch = CS_ARCH_RISCV;
            else if (equalsIgnoreCase(archName, "mos65xx"))
                arch = CS_ARCH_MOS65XX;
            else if (equalsIgnoreCase(archName, "bpf"))
                arch = CS_ARCH_BPF;
            else if (equalsIgnoreCase(archName, "sh") || equalsIgnoreCase(archName, "superh"))
                arch = CS_ARCH_SH;
            else if (equalsIgnoreCase(archName, "tricore"))
                arch = CS_ARCH_TRICORE;
        #if CS_API_MAJOR >= 6
            else if (equalsIgnoreCase(archName, "alpha"))
                arch = CS_ARCH_ALPHA;
            else if (equalsIgnoreCase(archName, "hppa"))
                arch = CS_ARCH_HPPA;
            else if (equalsIgnoreCase(archName, "loongarch"))
                arch = CS_ARCH_LOONGARCH;
            else if (equalsIgnoreCase(archName, "xtensa"))
                arch = CS_ARCH_XTENSA;
            else if (equalsIgnoreCase(archName, "arc"))
                arch = CS_ARCH_ARC;
        #endif
            else
                throw std::runtime_error("Invalid disassembler architecture");

            auto optionsVector = wolv::util::splitString(std::string(options), ",");
            for (std::string_view option : optionsVector) {
                option = wolv::util::trim(option);

                bool shouldAdd = true;
                if (option.starts_with("no-")) {
                    shouldAdd = false;
                    option.remove_prefix(3);
                }

                constexpr static std::array<std::pair<std::string_view, cs_mode>, 110> Options = {{
                    // Common
                    { "16bit",      CS_MODE_16                      },
                    { "32bit",      CS_MODE_32                      },
                    { "64bit",      CS_MODE_64                      },

                    // ARM
                    { "cortex-m",   CS_MODE_MCLASS                  },
                    { "armv8",      CS_MODE_V8                      },
                    { "thumb",      CS_MODE_THUMB                   },

                #if CS_API_MAJOR >= 6
                    // ARM64
                    { "apple",      CS_MODE_APPLE_PROPRIETARY       },
                #endif

                    // SPARC
                    { "sparcv9",    CS_MODE_V9                      },

                    // PowerPC
                    { "qpx",        CS_MODE_QPX                     },
                    { "spe",        CS_MODE_SPE                     },
                    { "ps",         CS_MODE_PS                      },
                    { "booke",      CS_MODE_BOOKE                   },
                #if CS_API_MAJOR >= 6
                    { "aixos",      CS_MODE_AIX_OS                  },
                    { "pwr7",       CS_MODE_PWR7                    },
                    { "pwr8",       CS_MODE_PWR8                    },
                    { "pwr9",       CS_MODE_PWR9                    },
                    { "pwr10",      CS_MODE_PWR10                   },
                    { "future",     CS_MODE_PPC_ISA_FUTURE          },
                    { "aixas",      CS_MODE_MODERN_AIX_AS           },
                    { "msync",      CS_MODE_MSYNC                   },
                #endif

                    // M68K
                    { "68000",      CS_MODE_M68K_000                },
                    { "68010",      CS_MODE_M68K_010                },
                    { "68020",      CS_MODE_M68K_020                },
                    { "68030",      CS_MODE_M68K_030                },
                    { "68040",      CS_MODE_M68K_040                },
                    { "68060",      CS_MODE_M68K_060                },

                    // MIPS
                    { "micromips",  CS_MODE_MICRO                   },
                    { "mips2",      CS_MODE_MIPS2                   },
                    { "mips3",      CS_MODE_MIPS3                   },
                    { "mips32r6",   CS_MODE_MIPS32R6                },
                #if CS_API_MAJOR >= 6
                    { "mips1",      CS_MODE_MIPS1                   },
                    { "mips4",      CS_MODE_MIPS4                   },
                    { "mips5",      CS_MODE_MIPS5                   },
                    { "mips32r2",   CS_MODE_MIPS32R2                },
                    { "mips32r3",   CS_MODE_MIPS32R3                },
                    { "mips32r5",   CS_MODE_MIPS32R5                },
                    { "mips64r2",   CS_MODE_MIPS64R2                },
                    { "mips64r3",   CS_MODE_MIPS64R3                },
                    { "mips64r5",   CS_MODE_MIPS64R5                },
                    { "mips64r6",   CS_MODE_MIPS64R6                },
                    { "octeon" ,    CS_MODE_OCTEON                  },
                    { "octeonp",    CS_MODE_OCTEONP                 },
                    { "nanomips",   CS_MODE_NANOMIPS                },
                    { "nms1",       CS_MODE_NMS1                    },
                    { "i7200",      CS_MODE_I7200                   },
                    { "nofloat",    CS_MODE_MIPS_NOFLOAT            },
                    { "ptr64",      CS_MODE_MIPS_PTR64              },
                    { "micro32r3",  CS_MODE_MICRO32R3               },
                    { "micro32r6",  CS_MODE_MICRO32R6               },
                #endif
                    { "6301",       CS_MODE_M680X_6301              },
                    { "6309",       CS_MODE_M680X_6309              },
                    { "6800",       CS_MODE_M680X_6800              },
                    { "6801",       CS_MODE_M680X_6801              },

                    // M680X
                    { "6301",       CS_MODE_M680X_6301              },
                    { "6309",       CS_MODE_M680X_6309              },
                    { "6800",       CS_MODE_M680X_6800              },
                    { "6801",       CS_MODE_M680X_6801              },
                    { "6805",       CS_MODE_M680X_6805              },
                    { "6808",       CS_MODE_M680X_6808              },
                    { "6809",       CS_MODE_M680X_6809              },
                    { "6811",       CS_MODE_M680X_6811              },
                    { "cpu12",      CS_MODE_M680X_CPU12             },
                    { "hcs08",      CS_MODE_M680X_HCS08             },

                    // BPF
                    { "bpfe",       CS_MODE_BPF_EXTENDED            },

                    // RISC-V
                    { "rv32g",      CS_MODE_RISCV32                 },
                    { "rv64g",      CS_MODE_RISCV64                 },
                    { "riscvc",     CS_MODE_RISCVC                  },

                    // MOS65XX
                    { "6502",       CS_MODE_MOS65XX_6502            },
                    { "65c02",      CS_MODE_MOS65XX_65C02           },
                    { "w65c02",     CS_MODE_MOS65XX_W65C02          },
                    { "65816",      CS_MODE_MOS65XX_65816           },
                    { "long-m",     CS_MODE_MOS65XX_65816_LONG_M    },
                    { "long-x",     CS_MODE_MOS65XX_65816_LONG_X    },

                    // SuperH
                    { "sh2",        CS_MODE_SH2                     },
                    { "sh2a",       CS_MODE_SH2A                    },
                    { "sh3",        CS_MODE_SH3                     },
                    { "sh4",        CS_MODE_SH4                     },
                    { "sh4a",       CS_MODE_SH4A                    },
                    { "shfpu",      CS_MODE_SHFPU                   },
                    { "shdsp",      CS_MODE_SHDSP                   },

                    // Tricore
                    { "tc1.1",      CS_MODE_TRICORE_110             },
                    { "tc1.2",      CS_MODE_TRICORE_120             },
                    { "tc1.3",      CS_MODE_TRICORE_130             },
                    { "tc1.3.1",    CS_MODE_TRICORE_131             },
                    { "tc1.6",      CS_MODE_TRICORE_160             },
                    { "tc1.6.1",    CS_MODE_TRICORE_161             },
                    { "tc1.6.2",    CS_MODE_TRICORE_162             },

                #if CS_API_MAJOR >= 6
                    // HP/PA
                    { "hppa1.1",   CS_MODE_HPPA_11                  },
                    { "hppa2.0",   CS_MODE_HPPA_20                  },
                    { "hppa2.0w",  CS_MODE_HPPA_20W                 },

                    // LoongArch
                    { "loongarch32", CS_MODE_LOONGARCH32            },
                    { "loongarch64", CS_MODE_LOONGARCH64            },

                    // SystemZ
                    { "arch8",     CS_MODE_SYSTEMZ_ARCH8            },
                    { "arch9",     CS_MODE_SYSTEMZ_ARCH9            },
                    { "arch10",    CS_MODE_SYSTEMZ_ARCH10           },
                    { "arch11",    CS_MODE_SYSTEMZ_ARCH11           },
                    { "arch12",    CS_MODE_SYSTEMZ_ARCH12           },
                    { "arch13",    CS_MODE_SYSTEMZ_ARCH13           },
                    { "arch14",    CS_MODE_SYSTEMZ_ARCH14           },
                    { "z10",       CS_MODE_SYSTEMZ_Z10              },
                    { "z196",      CS_MODE_SYSTEMZ_Z196             },
                    { "zec12",     CS_MODE_SYSTEMZ_ZEC12            },
                    { "z13",       CS_MODE_SYSTEMZ_Z13              },
                    { "z14",       CS_MODE_SYSTEMZ_Z14              },
                    { "z15",       CS_MODE_SYSTEMZ_Z15              },
                    { "z16",       CS_MODE_SYSTEMZ_Z16              },
                    { "generic",   CS_MODE_SYSTEMZ_GENERIC          },

                    // Xtensa
                    { "esp32",     CS_MODE_XTENSA_ESP32             },
                    { "esp32s2",   CS_MODE_XTENSA_ESP32S2           },
                    { "esp8266",   CS_MODE_XTENSA_ESP8266           },
                #endif
                }};

                bool optionFound = false;
                for (const auto &[optionName, optionValue] : Options) {
                    if (equalsIgnoreCase(option, optionName)) {
                        if (shouldAdd) mode |=  optionValue;
                        else           mode &= ~optionValue;

                        optionFound = true;
                        break;
                    }
                }

                if (!optionFound) {
                    throw std::runtime_error(fmt::format("Unknown disassembler option '{}'", option));
                }
            }

            return { cs_arch(arch), cs_mode(mode) };
        }
    };
}
