#ifndef PROTOTYPE_SRC_L1_CODEGEN_PIXEL_MASSES_H
#define PROTOTYPE_SRC_L1_CODEGEN_PIXEL_MASSES_H

#include "../codegen/codegen.h"
#include "../codegen/util_template_inst.h"

/* Used to generate both _at() and _cat() methods */
NODISCARD VecU8 generate_texture_data_method_at(SpanU8 tex, SpanU8 pixvec, SpanU8 memb, bool const_access) {
    return VecU8_fmt(
        "%s%s* %s_%sat(%s%s* self, size_t x, size_t y) {\n"
        SPACE "assert(x < self->width);\n"
        SPACE "return %s_%sat(&self->pixels, x + y * self->width);\n"
        "}\n\n",
        const_access ? cstr("const ") : cstr(""), memb, tex, const_access ? cstr("") : cstr("m"),
        const_access ? cstr("const ") : cstr(""), tex, pixvec, const_access ? cstr("") : cstr("m"));
}

/* `tex` is the type name of texture data type
 * `memb` is the type name of pixel data type */
NODISCARD VecU8 generate_texture_data_struct_and_necc_methods(SpanU8 tex, SpanU8 memb, SpanU8 luminosity_formula) {
    VecU8 g_pixvec = VecU8_fmt("Vec%s", memb);
    SpanU8 pixvec = VecU8_to_span(&g_pixvec);
        VecU8 res = VecU8_fmt(
        "typedef struct {\n"
        SPACE "%s pixels;\n"
        SPACE "size_t width;\n"
        SPACE "size_t height;\n"
        "} %s;\n\n", pixvec, tex);
    /* Method _new() */
    VecU8_append_vec(&res, VecU8_fmt(
        "%s %s_new(U32 width, U32 height) {\n"
        SPACE "assert(width == 0 || height == 0 ||\n"
        SPACE SPACE "!(SIZE_MAX / width / height < 100 || UINT32_MAX / width < 10 || UINT32_MAX / height < 10));\n"
        SPACE "return (%s){.pixels = %s_new_zeroinit((size_t)width * height), .width = width, .height = height};\n"
        "}\n\n", tex, tex, tex, pixvec));
    /* Method _drop() */
    VecU8_append_vec(&res, VecU8_fmt(
        "void %s_drop(%s self) {\n"
        SPACE "%s_drop(self.pixels);\n"
        "}\n\n", tex, tex, pixvec));
    /* Methods _at and _cat */
    VecU8_append_vec(&res, generate_texture_data_method_at(tex, pixvec, memb, false));
    VecU8_append_vec(&res, generate_texture_data_method_at(tex, pixvec, memb, true));
    /* Method _get_size_in_bytes */
    VecU8_append_vec(&res, VecU8_fmt(
        "size_t %s_get_size_in_bytes(const %s* self) {\n"
        SPACE "return self->pixels.len * sizeof(%s);\n"
        "}\n\n", tex, tex, memb));
    /* Method _to_bitmap_text()
     * We use the assumption that bytes in type member are tightly packed
     * Actually, our current method of texture read/write is super inefficient
     */
    VecU8_append_vec(&res, VecU8_fmt(
    "VecU8 %s_to_bitmap_text(const %s* self) {\n"
    SPACE "assert(SIZE_MAX / self->pixels.len >= 100);\n"
    SPACE "size_t len = self->pixels.len * sizeof(%s);\n"
    SPACE "VecU8 res = VecU8_new_zeroinit(8 + len);\n"
    SPACE "size_t width = self->width;\n"
    SPACE "size_t height = self->pixels.len / self->width;\n"
    SPACE "assert(UINT32_MAX / width >= 10 && UINT32_MAX / height >= 10);\n"
    SPACE "for (int i = 0; i < 4; i++)\n"
    SPACE SPACE "*VecU8_mat(&res, 0 + i) = (width >> (8 * i)) & 0xff;\n"
    SPACE "for (int i = 0; i < 4; i++)\n"
    SPACE SPACE "*VecU8_mat(&res, 4 + i) = (height >> (8 * i)) & 0xff;\n"
    SPACE "memcpy(res.buf + 8, self->pixels.buf, len);\n"
    SPACE "return res;\n"
    "}\n\n", tex, tex, memb));
    /* Method _write_to_file
     * Aborts on failure */
    VecU8_append_vec(&res, VecU8_fmt(
    "void %s_write_to_file(const %s* self, const char* path) {\n"
    SPACE "VecU8 data = %s_to_bitmap_text(self);\n"
    SPACE "write_whole_file_or_abort(path, VecU8_to_span(&data));\n"
    SPACE "VecU8_drop(data);\n"
    "}\n\n", tex, tex, tex));
    /* Result<tex, SpanU8> structure */
    VecU8 g_resoftex = get_ResultType_inst_name(tex, cstr("SpanU8"));
    SpanU8 resoftex = VecU8_to_span(&g_resoftex);
    VecU8_append_vec(&res, generate_result_template_inst(tex, cstr("SpanU8"), false, true));
    /* I also add this, because why not?? Maye I will use it in the future... */
    VecU8_append_vec(&res, generate_result_template_inst(tex, cstr("VecU8"), false, false));
    /* Method _from_bitmap_text()
     * We assume that bytes are tightly packed in member type */
    VecU8_append_vec(&res, VecU8_fmt(
    "%s %s_from_bitmap_text(SpanU8 text) {\n"
    SPACE "if (text.len < 8)\n"
    SPACE SPACE "return (%s){.variant = Result_Err, .err = cstr(\"No header *crying emoji*\")};\n"
    SPACE "size_t width = 0, height = 0;\n"
    SPACE "for (int i = 0; i < 4; i++)\n"
    SPACE SPACE "width |= (((size_t)*SpanU8_at(text, 0 + i)) << (8 * i));\n"
    SPACE "for (int i = 0; i < 4; i++)\n"
    SPACE SPACE "height |= (((size_t)*SpanU8_at(text, 4 + i)) << (8 * i));\n"
    SPACE "if (SIZE_MAX / width / height < 100 || UINT32_MAX / width < 10 || UINT32_MAX / height < 10)\n"
    SPACE SPACE "return (%s){.variant = Result_Err, .err = cstr(\"Image is too big\")};\n"
    SPACE "size_t len = width * height * sizeof(%s);\n"
    SPACE "if (text.len < 8 + len)\n"
    SPACE SPACE "return (%s){.variant = Result_Err, .err = cstr(\"Texture size and file size mismatch\")};\n"
    SPACE "%s res = %s_new(width, height);\n"
    SPACE "memcpy(res.pixels.buf, text.data + 8, len);\n"
    SPACE "return (%s){.variant = Result_Ok, .ok = res};\n"
    "}\n\n", resoftex, tex, resoftex, resoftex, memb, resoftex, tex, tex, resoftex));
    /* Method _read_from_file */
    VecU8_append_vec(&res, VecU8_fmt(
    "%s %s_read_from_file(SpanU8 path) {\n"
    SPACE "VecU8 data = read_whole_file_or_abort(path);\n"
    SPACE "%s res = %s_from_bitmap_text(VecU8_to_span(&data));\n"
    SPACE "if (res.variant != Result_Ok) {\n"
    SPACE SPACE "fprintf(stderr, \"Tried loading bitmap texture from file, but encountered decoding error: \");\n"
    SPACE SPACE "SpanU8_fprint(res.err, stderr);\n"
    SPACE SPACE "abortf(\"\\n\");\n"
    SPACE "}\n"
    SPACE "VecU8_drop(data);\n"
    SPACE "return res.ok;\n"
    "}\n\n", tex, tex, resoftex, tex));
    /* Method _is_inside() */
    VecU8_append_vec(&res, VecU8_fmt(
        "bool %s_is_inside(const %s* self, S32 x, S32 y) {\n"
        SPACE "return x >= 0 && y >= 0 && x < (S32)self->width && self->width * y + x < self->pixels.len;\n"
        "}\n\n", tex, tex));
    /* Method _print() */
    VecU8_append_vec(&res, VecU8_fmt(
    "void %s_print(const %s* self) {\n"   /* tex, tex */
    SPACE "U64 width = self->width;\n"
    SPACE "U64 height = self->height;\n"
    SPACE "U64 cell_width = MAX_U64(1, width / 190);\n"
    SPACE "U64 cell_height = MAX_U64(1, cell_width * 14 / 8);\n"
    SPACE "for (U64 CY = 0; CY < height; CY += cell_height) {\n"
    SPACE SPACE "for (U64 CX = 0; CX < width; CX += cell_width) {\n"
    SPACE SPACE SPACE "float lum = 0;\n"
    SPACE SPACE SPACE "for (U64 j = 0; j < cell_height; j++) {\n"
    SPACE SPACE SPACE SPACE "U64 y = CY + j;\n"
    SPACE SPACE SPACE SPACE "if (y >= height)\n"
    SPACE SPACE SPACE SPACE SPACE "continue;\n"
    SPACE SPACE SPACE SPACE "for (U64 i = 0; i < cell_width; i++) {\n"
    SPACE SPACE SPACE SPACE SPACE "U64 x = CX + i;\n"
    SPACE SPACE SPACE SPACE SPACE "if (x >= width)\n"
    SPACE SPACE SPACE SPACE SPACE SPACE "continue;\n"
    SPACE SPACE SPACE SPACE SPACE "%s pix = *%s_at(self, x, y);\n"       /* memb, tex */
    SPACE SPACE SPACE SPACE SPACE "lum += %s;\n"   /* luminosity_formula */
    SPACE SPACE SPACE SPACE "}\n"
    SPACE SPACE SPACE "}\n"
    SPACE SPACE SPACE "lum /= (float)cell_width * (float)cell_height;\n"
    SPACE SPACE SPACE "putc(lum > 0.95 ? '@' : (lum > 0.8 ? '#' : (lum > 0.65 ? '*' : (lum > 0.4 ? '_' : (lum > 0.2 ? '.' : ' ')))), stdout);\n"
    SPACE SPACE "}\n"
    SPACE SPACE "putc('\\n', stdout);\n"
    SPACE "}\n"
    "}\n\n", tex, tex, memb, tex, luminosity_formula));

    VecU8_drop(g_resoftex);
    VecU8_drop(g_pixvec);
    return res;
}

void generate_pixel_masses_header() {
    GeneratedHeader res = begin_header(cstr("l1/pixel_masses.h"));
    VecU8_append_span(&res.result, cstr("#include \"VecAndSpan_U8.h\"\n"));
    VecU8_append_span(&res.result, cstr("#include \"../../src/l1/system/fileio.h\"\n"));
    VecU8_append_span(&res.result, cstr("#include \"VecAndSpan_cvec3.h\"\n"));
    VecU8_append_span(&res.result, cstr("#include \"VecAndSpan_cvec4.h\"\n\n"));
    VecU8_append_vec(&res.result, generate_texture_data_struct_and_necc_methods(cstr("TextureDataR8"), cstr("U8"),
        cstr("(float)pix / 255")));
    VecU8_append_vec(&res.result, generate_texture_data_struct_and_necc_methods(cstr("TextureDataR8G8B8"), cstr("cvec3"),
        cstr("(float)pix.x / 255 * 0.21f + (float)pix.y / 255 * 0.71f + (float)pix.z / 255 * 0.08f")));
    VecU8_append_vec(&res.result, generate_texture_data_struct_and_necc_methods(cstr("TextureDataR8G8B8A8"), cstr("cvec4"),
        cstr("(float)pix.x / 255 * 0.21f + (float)pix.y / 255 * 0.71f + (float)pix.z / 255 * 0.08f")));
    finish_header(res);
}


#endif
