#ifndef PROTOTYPE1_SRC_L1_ANNE_MARGARET_PNG_PIXEL_MASSES_H
#define PROTOTYPE1_SRC_L1_ANNE_MARGARET_PNG_PIXEL_MASSES_H

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

typedef struct {
    S64 channel_count;
    SpanU8 name;
} color_type_name_in_png;

color_type_name_in_png color_types_names_in_png[] = {
    {1, cstr("PNG_COLOR_TYPE_GRAY")},
    {2, cstr("PNG_COLOR_TYPE_GRAY_ALPHA")},
    {3, cstr("PNG_COLOR_TYPE_RGB")},
    {4, cstr("PNG_COLOR_TYPE_RGBA")},
};

NODISCARD VecU8 generate_margaret_png_texture_data_methods(SpanU8 format_signature, S64 depth, S64 channel_count) {
    if (depth != 8)
        abortf("Please no\n");
    SpanU8 color_type = cstr("");
    for (size_t i = 0; i < ARRAY_SIZE(color_types_names_in_png); i++) {
        if (color_types_names_in_png[i].channel_count == channel_count)
            color_type = color_types_names_in_png[i].name;
    }
    if (color_type.len == 0)
        abortf("Please don't\n");

    U64 sizeof_pixel = (U64)depth / 8 * (U64)channel_count;
    VecU8 res = VecU8_new();

    VecU8 g_promise = VecU8_fmt("MargaretPromisedPng%s", format_signature);
    SpanU8 promise = VecU8_to_span(&g_promise);
    VecU8 g_tex = VecU8_fmt("TextureData%s", format_signature);
    SpanU8 tex = VecU8_to_span(&g_tex);

    VecU8_append_vec(&res, VecU8_fmt(
    "NODISCARD ResultVoidOrVecU8 %s_write_to_png(const %s* self, SpanU8 filename) {\n"        /* tex, tex */
    SPACE "VecU8 nt_filename = VecU8_fmt(\"%%s%%c\", filename, 0);\n"
    SPACE "FILE *fp = fopen((CSTR)nt_filename.buf, \"wb\");\n"
    SPACE "VecU8_drop(nt_filename);\n"
    SPACE "if (!fp) {\n"
    SPACE SPACE "return (ResultVoidOrVecU8){.variant = Result_Err, .err = VecU8_fmt(\"Unable to open file %%s\", filename)};\n"
    SPACE "}\n"
    SPACE "png_structp pngshka = png_create_write_struct(PNG_LIBPNG_VER_STRING,\n"
    SPACE SPACE "NULL, margaret_libpng_h_error_cb, margaret_libpng_h_warning_cb);\n"
    SPACE "if (!pngshka)\n"
    SPACE SPACE "abortf(\"png_create_write_struct\");\n"
    SPACE "png_infop info = png_create_info_struct(pngshka);\n"
    SPACE "if (!info)\n"
    SPACE SPACE"abortf(\"png_create_info_struct\");\n"
    SPACE "png_bytep* row_pointers = NULL;\n"
    SPACE "if (setjmp(png_jmpbuf(pngshka))){\n"
    SPACE SPACE "png_destroy_write_struct(&pngshka, &info);\n"
    SPACE SPACE "fclose(fp);\n"
    SPACE SPACE "free(row_pointers);\n"
    SPACE SPACE "return (ResultVoidOrVecU8){.variant = Result_Err, .err = VecU8_from_cstr(\"Some png error happened\")};\n"
    SPACE "}\n"
    SPACE "png_init_io(pngshka, fp);\n"
    SPACE "U32 width = self->width;\n"
    SPACE "U32 height = self->height;\n"
    SPACE "png_set_IHDR(pngshka, info, width, height, %i, %s,\n"                             /* depth, color_type */
    SPACE SPACE "PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);\n"
    SPACE "row_pointers = calloc(height, sizeof(row_pointers));\n"
    SPACE "for (U32 y = 0; y < height; y++) {\n"
    SPACE SPACE "row_pointers[height - 1 - y] = (png_bytep)((%s_at(self, 0, y)));\n"               /* tex */
    SPACE "}\n"
    SPACE "png_set_rows(pngshka, info, row_pointers);\n"
    SPACE "png_write_png(pngshka, info, 0, NULL);\n"
    SPACE "/* No more errors */\n"
    SPACE "free(row_pointers);\n"
    SPACE "png_destroy_write_struct(&pngshka, &info);\n"
    SPACE "fclose(fp);\n"
    SPACE "return (ResultVoidOrVecU8){.variant = Result_Ok};\n"
    "}\n\n"
    "/* Aborts on error */\n"
    "void %s_write_to_png_nofail(const %s* self, SpanU8 filename) {\n"                         /* tex, tex*/
    SPACE "ResultVoidOrVecU8 res = %s_write_to_png(self, filename);\n"                          /* tex */
    SPACE "if (res.variant == Result_Err) {\n"
    SPACE SPACE "SpanU8_fprint(VecU8_to_span(&res.err), stderr);\n"
    SPACE SPACE "abortf(\"      %s_write_to_png\\n\");\n"                                     /* tex */
    SPACE "}\n"
    "}\n",
    tex, tex, depth, color_type, tex, tex, tex, tex, tex));

    /* Non clonable structure */
    VecU8_append_vec(&res, VecU8_fmt(
    "typedef struct {\n"
    SPACE "FILE* fp;\n"
    SPACE "png_structp pngshka;\n"
    SPACE "png_infop info;\n"
    SPACE "png_infop end_info;\n"
    "} %s;\n\n"
    "void %s_drop(%s self) {\n"
    SPACE "png_destroy_read_struct(&self.pngshka, &self.info, &self.end_info);\n"
    SPACE "fclose(self.fp);\n"
    "}\n\n",
    promise, promise, promise));

    VecU8_append_vec(&res, generate_result_template_inst(promise, cstr("VecU8"), false, false));

    VecU8_append_vec(&res, VecU8_fmt(
    "NODISCARD Result%sOrVecU8 %s_begin(SpanU8 filename) {\n"                 /* promise, promise */
    SPACE "VecU8 nt_filename = VecU8_fmt(\"%%s%%c\", filename, 0);\n"
    SPACE "FILE* fp = fopen((CSTR)nt_filename.buf, \"rb\");\n"
    SPACE "VecU8_drop(nt_filename);\n"
    SPACE "if (!fp) {\n"
    SPACE SPACE "return (Result%sOrVecU8){.variant = Result_Err,\n"                         /* promise */
    SPACE SPACE SPACE ".err = VecU8_fmt(\"Unable to open file %%s\", filename)};\n"
    SPACE "}\n"
    SPACE "png_structp pngshka = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, margaret_libpng_h_error_cb, margaret_libpng_h_warning_cb);\n"
    SPACE "if (!pngshka)\n"
    SPACE SPACE "abortf(\"png_create_read_struct\");\n"
    SPACE "png_infop info = png_create_info_struct(pngshka);\n"
    SPACE "if (!info)\n"
    SPACE SPACE "abortf(\"png_create_info_struct\");\n"
    SPACE "png_infop end_info = png_create_info_struct(pngshka);\n"
    SPACE "if (!end_info)\n"
    SPACE SPACE "abortf(\"png_create_info_struct\");\n"
    SPACE "if (setjmp(png_jmpbuf(pngshka))) {\n"
    SPACE SPACE "png_destroy_read_struct(&pngshka, &info, &end_info);\n"
    SPACE SPACE "fclose(fp);\n"
    SPACE SPACE "return (Result%sOrVecU8){.variant = Result_Err,\n"                        /* promise  */
    SPACE SPACE SPACE ".err = VecU8_from_cstr(\"Some png error happened\")};\n"
    SPACE "}\n"
    SPACE "png_init_io(pngshka, fp);\n"
    SPACE "png_read_info(pngshka, info);\n"
    SPACE "U32 width, height;\n"
    SPACE "int bit_depth, color_type;\n"
    SPACE "check(png_get_IHDR(pngshka, info, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL));\n",
    promise, promise, promise, promise));

    /* We are still in PROMISE_begin method, now we need to do the conversion */
    if (depth == 8) {
        VecU8_append_span(&res, cstr(
        SPACE "if (bit_depth == 16)\n"
        SPACE SPACE "png_set_strip_16(pngshka);\n"
        SPACE "else if (color_type == PNG_COLOR_TYPE_GRAY && (bit_depth == 1 || bit_depth == 2 || bit_depth == 4))\n"
        SPACE SPACE "png_set_expand_gray_1_2_4_to_8(pngshka);\n"
        ));
    } else
        assert(false);
    if (channel_count == 3 || channel_count == 4) {
        VecU8_append_span(&res, cstr(
        SPACE "if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)\n"
        SPACE SPACE "png_set_gray_to_rgb(pngshka);\n"
        SPACE "else if (color_type == PNG_COLOR_TYPE_PALETTE)"
        SPACE SPACE "png_set_palette_to_rgb(pngshka);\n"
        ));
    } else if (channel_count == 1 || channel_count == 2) {
        VecU8_append_span(&res, cstr(
        SPACE "if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGBA) {\n"
        SPACE SPACE "png_set_rgb_to_gray_fixed(pngshka, 1, 21268, 71514);\n"
        SPACE "} else if (color_type == PNG_COLOR_TYPE_PALETTE) {\n"
        SPACE SPACE "png_set_palette_to_rgb(pngshka);\n"
        SPACE SPACE "png_set_rgb_to_gray_fixed(pngshka, 1, 21268, 71514);\n"
        SPACE "}\n"
        ));
    } else
        assert(false);
    if (channel_count == 4 || channel_count == 2) {
        VecU8_append_span(&res, cstr(
        SPACE "if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_PALETTE) {\n"
        SPACE SPACE "if (png_get_valid(pngshka, info, PNG_INFO_tRNS)) \n"
        SPACE SPACE SPACE "png_set_tRNS_to_alpha(pngshka);\n"
        SPACE SPACE "else\n"
        SPACE SPACE SPACE "png_set_add_alpha(pngshka, 0xFF, PNG_FILLER_AFTER);\n"
        SPACE "}\n"
        ));
    } else if (channel_count == 1 || channel_count == 3) {
        VecU8_append_span(&res, cstr(
        SPACE "if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA || color_type == PNG_COLOR_TYPE_RGBA)\n"
        SPACE SPACE "png_set_strip_alpha(pngshka);\n"
        ));
    } else
        assert(false);
    /* At this point we have a converted image png structure */

    VecU8_append_vec(&res, VecU8_fmt(
    SPACE "png_read_update_info(pngshka, info);\n"
    SPACE "{\n"
    SPACE SPACE "U32 new_width, new_height;\n"
    SPACE SPACE "int new_bit_depth, new_color_type;\n"
    SPACE SPACE "check(png_get_IHDR(pngshka, info, &new_width, &new_height, &new_bit_depth, &new_color_type, NULL, NULL, NULL));\n"
    SPACE SPACE "assert(new_width == width && new_height == height && new_bit_depth == %i && new_color_type == %s);\n"  /* depth, color_type */
    SPACE "}\n"
    SPACE "return (Result%sOrVecU8){.variant = Result_Ok,\n"                                /* promise */
    SPACE SPACE ".ok = (%s){.fp = fp, .pngshka = pngshka, .info = info, .end_info = end_info}};\n"   /* promise */
    "}\n\n",
    (S64)depth, color_type,
    promise, promise));

    VecU8_append_vec(&res, VecU8_fmt(
    "SizeOfRectangleU32 %s_get_extent(const %s* self) {\n"     /* promise, promise */
    SPACE "U32 width, height;\n"
    SPACE "int bit_depth, color_type;\n"
    SPACE "check(png_get_IHDR(self->pngshka, self->info, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL));\n"
    SPACE "return (SizeOfRectangleU32){width, height};\n"
    "}\n\n",
    promise, promise));

    assert(depth == 8 || depth == 16);
    assert(channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 3);

    VecU8_append_vec(&res, VecU8_fmt(
    "size_t %s_get_needed_buffer_size(const %s* self) {\n"                        /* promise, promise */
    SPACE "SizeOfRectangleU32 dim = %s_get_extent(self);\n"                       /* promise */
    SPACE "return %u * dim.width * dim.height;\n"                        /* sizeof_pixel  */
    "}\n\n",
    promise, promise, promise, sizeof_pixel));

    VecU8_append_vec(&res, VecU8_fmt(
    "NODISCARD ResultVoidOrVecU8 %s_finish(%s self, void* buffer) {\n"                   /* promise, promise */
    SPACE "SizeOfRectangleU32 dim = %s_get_extent(&self);\n"                    /* promise */
    SPACE "png_bytep* row_pointers = NULL;\n"
    SPACE "if (setjmp(png_jmpbuf(self.pngshka))) {\n"
    SPACE SPACE "png_destroy_read_struct(&self.pngshka, &self.info, &self.end_info);\n"
    SPACE SPACE "fclose(self.fp);\n"
    SPACE SPACE "free(row_pointers);\n"
    SPACE SPACE "return (ResultVoidOrVecU8){.variant = Result_Err, .err = VecU8_from_cstr(\"Some png error happened\")};\n"
    SPACE "}\n"
    SPACE "row_pointers = calloc(dim.height, sizeof(row_pointers));\n"
    SPACE "for (U32 y = 0; y < dim.height; y++) {\n"
    SPACE SPACE "row_pointers[dim.height - 1 - y] = ((png_bytep)buffer) + %u * dim.width * y;\n"      /* sizeof_pixel */
    SPACE "}\n"
    SPACE "png_read_image(self.pngshka, row_pointers);\n"
    SPACE "png_read_end(self.pngshka, self.end_info);\n"
    SPACE "/* No more errors */\n"
    SPACE "png_destroy_read_struct(&self.pngshka, &self.info, &self.end_info);\n"
    SPACE "fclose(self.fp);\n"
    SPACE "free(row_pointers);\n"
    SPACE "return (ResultVoidOrVecU8){.variant = Result_Ok};\n"
    "}\n\n",
    promise, promise, promise, sizeof_pixel
    ));

    VecU8_append_vec(&res, VecU8_fmt(
    "NODISCARD Result%sOrVecU8 %s_finish_into_%s(%s self) {\n"           /* tex, promise, tex, promise */
    SPACE "SizeOfRectangleU32 dim = %s_get_extent(&self);\n"                      /* promise */
    SPACE "if (dim.width >= UINT32_MAX / 10 || dim.height >= UINT32_MAX / 10) {\n"
    SPACE SPACE "return (Result%sOrVecU8){.variant = Result_Err, .err = vcstr(\"Input image is too big\")};\n"      /* tex */
    SPACE "}\n"
    SPACE "%s tex = %s_new(dim.width, dim.height);\n"                               /* tex, tex */
    SPACE "ResultVoidOrVecU8 res = %s_finish(self, tex.pixels.buf);\n"                /* promise */
    SPACE "if (res.variant == Result_Err) {\n"
    SPACE SPACE "%s_drop(tex);\n"                                                     /* tex */
    SPACE SPACE "return (Result%sOrVecU8){.variant = Result_Err, .err = res.err};\n"      /* tex */
    SPACE "}\n"
    SPACE "return (Result%sOrVecU8){.variant = Result_Ok, .ok = tex};\n"                /* tex */
    "}\n\n",
    tex, promise, tex, promise, promise, tex, tex, tex, promise, tex, tex, tex
    ));

    VecU8_append_vec(&res, VecU8_fmt(

    "/* aborts on error */\n"
    "NODISCARD %s %s_read_from_png_nofail(SpanU8 name) {\n"                                  /* tex, tex */
    SPACE "Result%sOrVecU8 res_1 = %s_begin(name);\n"                            /* promise, promise */
    SPACE "if (res_1.variant == Result_Err) {\n"
    SPACE SPACE "SpanU8_fprint(VecU8_to_span(&res_1.err), stderr);\n"
    SPACE SPACE "abortf(\"     MargaretPromisedPng_begin\\n\");\n"
    SPACE "}\n"
    SPACE "/* res_1 invalidated, we moved ownership to _finish methos */\n"
    SPACE "Result%sOrVecU8 res_2 = %s_finish_into_%s(res_1.ok);\n"            /* tex, promise, tex */
    SPACE "if (res_2.variant == Result_Err) {\n"
    SPACE SPACE "SpanU8_fprint(VecU8_to_span(&res_2.err), stderr);\n"
    SPACE SPACE "abortf(\"    MargaretPromisedPng_finish (into TextureData)\\n\");\n"
    SPACE "}\n"
    SPACE "return res_2.ok;\n"
    "}\n\n",
    tex, tex, promise, promise, tex, promise, tex
    ));

    VecU8_drop(g_promise);
    VecU8_drop(g_tex);
    return res;
}

void generate_margaret_png_pixel_masses_header() {
    GeneratedHeader header =  begin_header(cstr("l1/margaret/png_pixel_masses.h"));

    VecU8_append_span(&header.result, cstr(
        "#include \"../pixel_masses.h\"\n"
        "#include \"../ResultVoidOrVecU8.h\"\n"
        "#include <png.h>\n\n"));

    VecU8_append_span(&header.result, cstr(
        "void margaret_libpng_h_error_cb(png_structp pngshka, png_const_charp err) {\n"
        SPACE "printf(\"[!] %s\\n\", err);\n"
        "}\n\n"
        "void margaret_libpng_h_warning_cb(png_structp pngshka, png_const_charp warning) {\n"
        SPACE "printf(\"[.] %s\\n\", warning);\n"
        "}\n\n"));

    VecU8_append_vec(&header.result, generate_margaret_png_texture_data_methods(cstr("R8"), 8, 1));
    VecU8_append_vec(&header.result, generate_margaret_png_texture_data_methods(cstr("R8G8B8"), 8, 3));
    VecU8_append_vec(&header.result, generate_margaret_png_texture_data_methods(cstr("R8G8B8A8"), 8, 4));

    finish_header(header);
}

#endif