/* This file is used for testing purposes only. Do not copy this file to installation prefix. */
#ifndef LIBREGEXIS024_BYTE_CODE_DISASSEMBLER_H
#define LIBREGEXIS024_BYTE_CODE_DISASSEMBLER_H

#include "vibe_check.h"

#include <libregexis024vm/vm_opcodes.h>
#include <libregexis024vm/utils.h>
#include <string>
#include <vector>
#include <map>
#include <stdio.h>
#include <inttypes.h>
#include <stdexcept>

using namespace regexis024;

struct landing_place_resolvance{
    size_t name_id;
    bool visited = false;
    landing_place_resolvance() = default;
    landing_place_resolvance(size_t nameId, bool visited) : name_id(nameId), visited(visited) {}
};

void print_disassembly(size_t prgSize, uint8_t* prg){
    std::vector<std::string> names = {
            "Александр", "Мария", "Иван", "Анна", "Дмитрий", "Екатерина", "Алексей",
            "Ольга", "Михаил", "София", "Сергей", "Анастасия", "Артем", "Виктория",
            "Андрей", "Елена", "Максим", "Алиса", "Павел", "Наталья", "Денис", "Юлия",
            "Владимир", "Маргарита", "Никита", "Дарья", "Илья", "Алина", "Роман", "Евгения",
            "Кирилл", "Елизавета", "Антон", "Татьяна", "Владислав", "Валерия", "Георгий",
            "Ксения", "Арсений", "Милана", "Даниил", "Вероника", "Тимофей", "Арина",
            "Николай", "Кристина", "Степан", "Алёна", "Игорь", "Алла", "Григорий", "Ева",
            "Олег", "Яна", "Семен", "Марина", "Федор", "Светлана", "Василий", "Людмила"
    };
    uint64_t used_names = 0;
    /* From program position -> to names[ind] & */
    std::map<near_ptr_t, landing_place_resolvance> bookmarks;
    near_ptr_t IP = 0;

    auto check_inboundness = [&](int region){
        if (!vmprog_check_inboundness(prgSize, IP, region)) {
            fprintf(stderr, "This program can't be decomposed into commands in a trivial way");
            std::terminate();
        }
    };
    auto extract_b = [&]() -> uint8_t{
        check_inboundness(1);
        return vmprog_extract_b(&IP, prg);
    };
    auto extract_w = [&]() -> uint16_t {
        check_inboundness(2);
        return vmprog_extract_w(&IP, prg);
    };
    auto extract_dw = [&]() -> uint32_t {
        check_inboundness(4);
        return vmprog_extract_dw(&IP, prg);
    };
    auto extract_qw = [&]() -> uint64_t {
        check_inboundness(8);
        return vmprog_extract_qw(&IP, prg);
    };
    auto extract_instruction = [&]() -> uint8_t{
        return extract_b();
    };
    auto extract_sslot_id = [&]() -> sslot_id_t{
        return extract_dw();
    };
    auto extract_near_pointer = [&]() -> near_ptr_t{
        return extract_qw();
    };
    auto extract_track_array_index = [&]() -> tai_t{
        return extract_w();
    };

    bool second_phase = false;

    auto fph_register_landing = [&](near_ptr_t pos){
        if (!second_phase){
            if (bookmarks.count(pos) == 0){
                if (used_names == names.size())
                    names.push_back("Закладка_" + std::to_string(used_names));
                bookmarks.insert({pos, {used_names, false}});
                used_names++;
            }
        }
    };

    auto get_bookmark_in_2phase = [&](near_ptr_t pos) -> std::string {
        if (bookmarks.count(pos) == 0) {
            fprintf(stderr, "Bruh\n");
            std::terminate();
        }
        return names[bookmarks[pos].name_id];
    };

    auto one_reading = [&](){
        while (IP < prgSize) {
            near_ptr_t start_pos = IP;
            if (second_phase){
                if (bookmarks.count(IP) != 0){
                    printf("%s:\n", get_bookmark_in_2phase(IP).c_str());
                    bookmarks[IP].visited = true;
                }
            }
            uint8_t opcode = extract_instruction();
            switch (opcode) {
#define secPrint(fmt, ...) if (second_phase) {printf("% 3lu) " fmt, start_pos, __VA_ARGS__);} } break;
#define secPrintNoArg(str) if (second_phase) {printf("% 3lu) " str, start_pos);} } break;
#define instCase(oper_code) case opcodes::oper_code: {
#define jcMess(cond, sz_uppercase, x_t, extract_method, printf_sign) \
    instCase(JC ## cond ## _ ## sz_uppercase) \
        x_t x = extract_method(); \
        near_ptr_t dest = extract_near_pointer(); \
        fph_register_landing(dest); \
    secPrint("JC" #cond "_" #sz_uppercase " %" printf_sign " $%s\n", x, get_bookmark_in_2phase(dest).c_str())
#define jcCacaphony(cond) \
    jcMess(cond, B, uint8_t, extract_b, PRIu8) \
    jcMess(cond, W, uint16_t, extract_w, PRIu16) \
    jcMess(cond, DW, uint32_t, extract_dw, PRIu32) \
    jcMess(cond, QW, uint64_t, extract_qw, PRIu64)
#define simpleDimple(name) instCase(name) secPrintNoArg(#name "\n")

                instCase(READ)
                    uint32_t ssid = extract_sslot_id();
                    secPrint("READ %u\n", ssid)
                simpleDimple(READZ)
                instCase(JUMP)
                    uint32_t dest = extract_near_pointer();
                    fph_register_landing(dest);
                secPrint("JUMP $%s\n", get_bookmark_in_2phase(dest).c_str())

                jcCacaphony(EQUAL)
                jcCacaphony(LESS)
                jcCacaphony(GRTR)

                instCase(FORK)
                    uint32_t ssid = extract_sslot_id();
                    near_ptr_t dest = extract_near_pointer();
                    fph_register_landing(dest);
                secPrint("FORK %u $%s\n", ssid, get_bookmark_in_2phase(dest).c_str())
                simpleDimple(MATCH)
                simpleDimple(DIE)
                instCase(PARAM_READ_SS_NUMBER)
                    sslot_id_t ssid_max_plus_one = extract_sslot_id();
                secPrint("PARAM_READ_SS_NUMBER %u\n", ssid_max_plus_one)
                instCase(PARAM_FORK_SS_NUMBER)
                    sslot_id_t ssid_max_plus_one = extract_sslot_id();
                secPrint("PARAM_FORK_SS_NUMBER %u\n", ssid_max_plus_one)
                instCase(PARAM_SELARR_LEN)
                    tai_t tai_max_plus_one = extract_track_array_index();
                secPrint("PARAM_SELARR_LEN %hu\n", tai_max_plus_one)
                instCase(PARAM_COLSIFTFUNC_SET)
                    near_ptr_t entry = extract_near_pointer();
                    fph_register_landing(entry);
                secPrint("PARAM_COLSIFTFUNC_SET $%s\n", get_bookmark_in_2phase(entry).c_str())
                simpleDimple(PARAM_COLSIFTFUNC_WIPE)
                instCase(MSG_MULTISTART_ALLOWED)
                    uint8_t is_allowed = extract_b();
                secPrint("MSG_MULTISTART_ALLOWED %hhu\n", is_allowed)
                instCase(MSG_FED_INPUT_EXTENDED)
                    uint8_t left = extract_b();
                    uint8_t right = extract_b();
                    sslot_id_t part = extract_sslot_id();
                secPrint("MSG_FED_INPUT_EXTENDED %hhu %hhu %u\n", left, right, part)
                instCase(DMOV_RABX_SELARR)
                    tai_t i = extract_track_array_index();
                secPrint("DMOV_RABX_SELARR %hu\n", i)
                instCase(DDIST_RABX_SELARR)
                    tai_t s = extract_track_array_index();
                    tai_t e = extract_track_array_index();
                secPrint("DDIST_RABX_SELARR %hu %hu\n", s, e);
                simpleDimple(SIFTPRIOR_MIN_RABX)
                simpleDimple(SIFTPRIOR_MAX_RABX)
                simpleDimple(SIFT_DONE)
                instCase(MOV_COLARR_IMM)
                    tai_t tai = extract_track_array_index();
                    uint64_t imm = extract_qw();
                secPrint("MOV_COLARR_IMM %hu %lu\n", tai, imm);
                instCase(MOV_COLARR_BTPOS)
                    tai_t tai = extract_track_array_index();
                secPrint("MOV_COLARR_BTPOS %hu\n", tai);
                instCase(MOV_SELARR_IMM)
                    tai_t tai = extract_track_array_index();
                    uint64_t imm = extract_qw();
                secPrint("MOV_SELARR_IMM %hu %lu\n", tai, imm);
                instCase(MOV_SELARR_CHPOS)
                    tai_t tai = extract_track_array_index();
                secPrint("MOV_SELARR_CHPOS %hu\n", tai);
                simpleDimple(INIT)
                simpleDimple(THROW)
                default:
                    fprintf(stderr, "Bad opcode\n");
                    std::terminate();
#undef secPrint
#undef secPrintNoArg
#undef instCase
#undef jcMess
#undef jcCacaphony
#undef simpleDimple
            }
        }
    };

    one_reading();
    second_phase = true;
    IP = 0;
    one_reading();
}

#endif //LIBREGEXIS024_BYTE_CODE_DISASSEMBLER_H