#ifndef PROTOTYPE1_SRC_L1_5_CODEGEN_TRAIT_BOX_AND_REF_H
#define PROTOTYPE1_SRC_L1_5_CODEGEN_TRAIT_BOX_AND_REF_H

#include "../../l1/codegen/codegen.h"

typedef struct {
    SpanU8 type;
    SpanU8 name;
} NamedVariableRecordRef;

#include "../../../gen/l1/eve/embassy_l1_5/SpanNamedVariableRecordRef.h"

typedef struct {
    bool takes_self;
    bool takes_mut_self;
    SpanNamedVariableRecordRef params;
    SpanU8 return_type;
    SpanU8 name;
} NamedMethodSignatureRecordRef;

#include "../../../gen/l1/eve/embassy_l1_5/SpanNamedMethodSignatureRecordRef.h"

typedef struct {
    SpanNamedMethodSignatureRecordRef methods;
    bool drop_primitive;
    SpanU8 name;
    /* It is usually either name or "void" */
    SpanU8 base_struct_name;
} NamedTraitDefRecordRef;

NODISCARD VecU8 generate_trait_table_structure(NamedTraitDefRecordRef trait){
    VecU8 res = VecU8_from_cstr("typedef struct {\n");
    for (size_t i = 0; i < trait.methods.len; i++) {
        NamedMethodSignatureRecordRef method = *SpanNamedMethodSignatureRecordRef_at(trait.methods, i);
        VecU8_append_vec(&res, VecU8_fmt(SPACE "%s (*%s)(", c_type_empty_means_void(method.return_type), method.name));
        if (method.takes_self) {
            VecU8_append_vec(&res, VecU8_fmt(
                method.takes_mut_self ? "%s*" : "const %s*",
                c_type_empty_means_void(trait.base_struct_name)));
        }
        for (size_t p = 0; p < method.params.len; p++) {
            NamedVariableRecordRef param = *SpanNamedVariableRecordRef_at(method.params, p);
            if (p || method.takes_self)
                VecU8_append_span(&res, cstr(", "));
            VecU8_append_span(&res, param.type);
        }
        VecU8_append_span(&res, cstr(");\n"));
    }
    if (!trait.drop_primitive) {
        VecU8_append_vec(&res, VecU8_fmt(
            SPACE "void (*drop)(%s*);\n",
            c_type_empty_means_void(trait.base_struct_name)));
    }
    VecU8_append_vec(&res, VecU8_fmt("} %s_Table;\n\n", trait.name));
    return res;
}

typedef struct {
    NamedTraitDefRecordRef trait;
    bool box;
    bool ref;
    bool mut_ref;
} trait_wrapper_boil_options;

/* (refkind, mut) in {(Ref, false), (MutRef, true), (Box, true)} */
void codegen_append_trait_wrapper_structure_some_refkind(VecU8* res, NamedTraitDefRecordRef trait,
    SpanU8 refkind, bool mut){
    VecU8_append_vec(res, VecU8_fmt(
        "typedef struct {\n"
        SPACE "%s%s* r;\n"              /* epsilon / const,  op.trait.base_struct_name */
        SPACE "const %s_Table* t;\n"    /* op.trait.name */
        "} %s%s;\n\n",                 /* refkind, op.trait.name */
        mut ? cstr("") : cstr("const "),
        c_type_empty_means_void(trait.base_struct_name),
        trait.name, refkind, trait.name));
}

/* (refkind, self_as_ptr) in {(Ref, false), (MutRef, false), (Box, true)} */
void codegen_append_trait_wrapper_some_method_some_refkind(VecU8* res, SpanU8 trait_name,
    NamedMethodSignatureRecordRef method, SpanU8 refkind, bool self_as_ptr){
    VecU8_append_vec(res, VecU8_fmt(
        "%s %s%s_%s(%s%s%s self",
        /* return_type, refkind, trait.name, method.name, refkind, trait.name */
        c_type_empty_means_void(method.return_type),
        refkind, trait_name, method.name, refkind, trait_name, self_as_ptr ? cstr("*") : cstr("")));
    for (size_t p = 0; p < method.params.len; p++) {
        NamedVariableRecordRef param = method.params.data[p];
        VecU8_append_vec(res, VecU8_fmt(", %s %s", param.type, param.name));
    }
    VecU8_append_span(res, cstr("){\n" SPACE));
    if (method.return_type.len > 0)
        VecU8_append_span(res, cstr("return "));
    VecU8_append_vec(res, VecU8_fmt("self%s""t->%s(",
        self_as_ptr ? cstr("->") : cstr("."),
        method.name));
    if (method.takes_self) {
        VecU8_append_vec(res, VecU8_fmt("self%s""r", self_as_ptr ? cstr("->") : cstr(".")));
    }
    for (size_t p = 0; p < method.params.len; p++) {
        NamedVariableRecordRef param = method.params.data[p];
        if (p > 0 || method.takes_self)
            VecU8_append_span(res, cstr(", "));
        VecU8_append_span(res, param.name);
    }
    VecU8_append_span(res, cstr(");\n}\n\n"));
}

NODISCARD VecU8 generate_trait_wrapper_boilerplate(trait_wrapper_boil_options op) {
    /* Checking */
    for (size_t i = 0; i < op.trait.methods.len; i++) {
        NamedMethodSignatureRecordRef method = op.trait.methods.data[i];
        assert(!method.takes_mut_self || method.takes_self);
    }

    VecU8 res = VecU8_new();
    VecU8_append_vec(&res, generate_trait_table_structure(op.trait));
    if (op.ref) {
        codegen_append_trait_wrapper_structure_some_refkind(&res, op.trait, cstr("Ref"), false);
        for (size_t i = 0; i < op.trait.methods.len; i++) {
            NamedMethodSignatureRecordRef method = op.trait.methods.data[i];
            if (method.takes_mut_self)
                continue;
            codegen_append_trait_wrapper_some_method_some_refkind(&res, op.trait.name, method, cstr("Ref"), false);
        }
    }
    if (op.mut_ref) {
        codegen_append_trait_wrapper_structure_some_refkind(&res, op.trait, cstr("MutRef"), true);
        for (size_t i = 0; i < op.trait.methods.len; i++) {
            NamedMethodSignatureRecordRef method = op.trait.methods.data[i];
            codegen_append_trait_wrapper_some_method_some_refkind(&res, op.trait.name, method, cstr("MutRef"), false);
        }
    }
    if (op.box) {
        codegen_append_trait_wrapper_structure_some_refkind(&res, op.trait, cstr("Box"), true);
        for (size_t i = 0; i < op.trait.methods.len; i++) {
            NamedMethodSignatureRecordRef method = op.trait.methods.data[i];
            codegen_append_trait_wrapper_some_method_some_refkind(&res, op.trait.name, method, cstr("Box"), true);
        }
        VecU8_append_vec(&res, VecU8_fmt(
            "void Box%s_drop(Box%s self){\n"  /* trait.name, trait.name */
            "%s"                              /* epsilon / calling self.t->drop() */
            SPACE "free(self.r);\n"
            "}\n\n",
            op.trait.name, op.trait.name,
            op.trait.drop_primitive ? cstr("") : cstr(SPACE "self.t->drop(self.r);\n")));
        if (op.ref) {
            VecU8_append_vec(&res, VecU8_fmt(
                "Ref%s Box%s_ref(Box%s *self) {\n"
                SPACE "return (Ref%s){.r = self->r, .t = self->t};\n"
                "}\n\n", op.trait.name, op.trait.name, op.trait.name, op.trait.name));
        }
        if (op.mut_ref) {
            VecU8_append_vec(&res, VecU8_fmt(
                "MutRef%s Box%s_mut_ref(Box%s *self) {\n"
                SPACE "return (MutRef%s){.r = self->r, .t = self->t};\n"
                "}\n\n", op.trait.name, op.trait.name, op.trait.name, op.trait.name));
        }
    }
    return res;
}

void generate_trait_wrapper_templ_inst_eve_header(SpanU8 layer, SpanU8 bonus_ns, trait_wrapper_boil_options op){
    generate_SOME_templ_inst_eve_header(layer, bonus_ns, generate_trait_wrapper_boilerplate(op),
        VecU8_from_span(op.trait.name));
}

void generate_trait_wrapper_templ_inst_guarded_header(SpanU8 layer, SpanU8 bonus_ns, SpanU8 dependencies,
    trait_wrapper_boil_options op){
    generate_SOME_templ_inst_guarded_header(layer, bonus_ns,
        VecU8_fmt("#include <stdlib.h>\n%s", dependencies),
        generate_trait_wrapper_boilerplate(op), VecU8_from_span(op.trait.name));
}

#endif