#ifndef PROTOTYPE1_SRC_L1_CODEGEN_CODEGEN_H
#define PROTOTYPE1_SRC_L1_CODEGEN_CODEGEN_H

#include <stdio.h>
#include "../system/fileio.h"
#include "../system/fsmanip.h"
#include "../core/VecU8_as_str.h"

typedef struct {
    VecU8 result;
    VecU8 nt_filename;
} GeneratedHeader;

NODISCARD GeneratedHeader begin_header(SpanU8 filename) {
    VecU8 res = VecU8_new();
    VecU8 guard = VecU8_from_cstr("PROTOTYPE1_GEN_");
    for (size_t i = 0; i < filename.len; i++) {
        char ch = (char)*SpanU8_at(filename, i);
        if (ch == '.' || ch == '/' || ch == '-' || ch == ',')
            VecU8_append(&guard, '_');
        else
            VecU8_append(&guard, ch);
    }
    VecU8_append_span(&res, cstr("#ifndef "));
    VecU8_append_span(&res, VecU8_to_span(&guard));
    VecU8_append_span(&res, cstr("\n#define "));
    VecU8_append_vec(&res, guard);
    VecU8_append_span(&res, cstr("\n/* Automatically generated file. Do not edit it. */\n\n"));
    return (GeneratedHeader){.result = res, .nt_filename = VecU8_fmt("%s%c", filename, 0) };
}

/* Codegen script's working directory should be `gen` */
void finish_header(GeneratedHeader header) {
    VecU8_append_span(&header.result, cstr("#endif\n"));
    write_whole_file_or_abort((const char*)header.nt_filename.buf, VecU8_to_span(&header.result));
    VecU8_drop(header.nt_filename);
    VecU8_drop(header.result);
}

#define SPACE "    "
#define SPACE8 "        "
#define SPACE12 "            "
#define SPACE16 "                "

void finish_layer(SpanU8 layer_name) {
    VecU8 nt_name = VecU8_fmt("%s/dorothy.txt%c", layer_name, 0);
    write_whole_file_or_abort((const char*)nt_name.buf, cstr(""));
    VecU8_drop(nt_name);
}

int get_number_of_parts_in_header_namespace(SpanU8 ns) {
    int a = 0;
    for (size_t i = 0; i < ns.len; i++) {
        if (*SpanU8_at(ns, i) != '/' && (i == 0 || *SpanU8_at(ns, i - 1) == '/'))
            a++;
    }
    return a;
}

/* takes ownership of strings a, b */
NODISCARD VecU8 codegen_T_ref_equal_T_ref(SpanU8 T, bool t_integer, VecU8 a, VecU8 b){
    if (t_integer)
        return VecU8_fmt("(%s == %s)", VecU8_to_span(&a), VecU8_to_span(&b));
    return VecU8_fmt("%s_equal_%s(%v, %v)", T, T, a, b);
}

/* takes ownership of strings a, b */
NODISCARD VecU8 codegen_T_ref_less_T_ref(SpanU8 T, bool t_integer, VecU8 a, VecU8 b){
    if (t_integer)
        return VecU8_fmt("(%s < %s)", VecU8_to_span(&a), VecU8_to_span(&b));
    return VecU8_fmt("%s_equal_%s(%v, %v)", T, T, a, b);
}

NODISCARD VecU8 prepend_spaces_to_SpanU8_lines(SpanU8 lines, int tabulation){
    VecU8 res = VecU8_new();
    if (lines.len > 0)
        for (int j = 0; j < tabulation; j++)
            VecU8_append_span(&res, cstr(SPACE));
    for (size_t i = 0; i < lines.len; i++) {
        VecU8_append(&res, lines.data[i]);
        if (lines.data[i] == '\n' && i + 1 < lines.len)
            for (int j = 0; j < tabulation; j++)
                VecU8_append_span(&res, cstr(SPACE));
    }
    return res;
}

void generate_SOME_templ_inst_eve_header(SpanU8 layer, SpanU8 bonus_ns, VecU8 body, VecU8 name){
    VecU8 text = VecU8_fmt("/* Automatically generated file. Don't edit it.\n"
                           "* Don't include it in more than one place */\n\n%v", body);
    VecU8 nt_path = VecU8_fmt("%s/eve/%s/%v.h%c", layer, bonus_ns, name, 0);
    write_whole_file_or_abort((const char*)nt_path.buf, VecU8_to_span(&text));
    VecU8_drop(nt_path);
    VecU8_drop(text);
}

void generate_SOME_templ_inst_guarded_header(SpanU8 layer, SpanU8 bonus_ns, VecU8 all_dependencies, VecU8 body, VecU8 name){
    assert(layer.len > 1);
    VecU8 path = VecU8_fmt("%s/%s%s%v.h", layer, bonus_ns, bonus_ns.len ? cstr("/") : cstr(""), name);
    GeneratedHeader head = begin_header(VecU8_to_span(&path));
    VecU8_drop(path);
    VecU8_append_vec(&head.result, all_dependencies);
    VecU8_append_span(&head.result, cstr("\n"));
    VecU8_append_vec(&head.result, body);
    finish_header(head);
}

/* Assumed we are at some_layer/bonus_ns/header.h header */
NODISCARD VecU8 codegen_include_relative_to_root(SpanU8 bonus_ns, SpanU8 abs_path){
    VecU8 res = vcstr("#include \"../../");
    int to_my_layer = get_number_of_parts_in_header_namespace(bonus_ns);
    for (int i = 0; i < to_my_layer; i++)
        VecU8_append_span(&res, cstr("../"));
    VecU8_append_span(&res, abs_path);
    VecU8_append_span(&res, cstr("\"\n"));
    return res;
}

#endif
