#define _GNU_SOURCE

#include <stddef.h>
#include <stdio.h>
#include <uchar.h>

#include "stb_image/stb_image.h"
#include "stb_image/stb_image_write.h"
#include "wcwidth/wcwidth.h"

#include "base64.h"
#include "colors.h"
#include "util.h"
#include "vt.h"
#include "vt_sixel.h"

typedef enum
{
    UL_NONE,
    UL_NORMAL,
    UL_DOUBLE,
    UL_CURLY,
} ul_style_t;

typedef struct
{
    ColorRGB         bg;
    ColorRGB         fg;
    ColorRGB         ul;
    enum VtRuneStyle rstyle;
    ul_style_t       ulstyle;
    bool             strikethrough;
    bool             overline;
    bool             blink;
} HtmlRuneState;

HtmlRuneState HtmlRuneState_from_vt_rune(const Vt* vt, VtRune* rune)
{
    HtmlRuneState ret = {
        .bg            = ColorRGB_from_RGBA(Vt_rune_bg(vt, rune)),
        .fg            = Vt_rune_fg(vt, rune),
        .ul            = Vt_rune_ln_clr(vt, rune),
        .rstyle        = rune->rune.style,
        .ulstyle       = UL_NONE,
        .strikethrough = rune->strikethrough,
        .overline      = rune->overline,
        .blink         = rune->blinkng,
    };

    if (rune->underlined) {
        ret.ulstyle = UL_NORMAL;
    } else if (rune->doubleunderline) {
        ret.ulstyle = UL_DOUBLE;
    } else if (rune->curlyunderline) {
        ret.ulstyle = UL_CURLY;
    }

    return ret;
}

static void start_span(Vector_char*     lines,
                       const char*      opt_class,
                       ColorRGB*        opt_bg,
                       ColorRGB*        opt_fg,
                       ColorRGB*        opt_ln,
                       enum VtRuneStyle style,
                       ul_style_t       ul_style,
                       bool             strikethrough,
                       bool             overline,
                       bool             blink)
{
    Vector_pushv_char(lines, "<span", strlen("<span"));

    if (opt_class) {
        Vector_pushv_char(lines, " class=\"", strlen(" class=\'"));
        Vector_pushv_char(lines, opt_class, strlen(opt_class));
        Vector_push_char(lines, '\"');
    }

    if (blink) {
        Vector_pushv_char(lines, " class=\"blink\"", strlen(" class=\"blink\""));
    }

    if (opt_bg || opt_fg || style != VT_RUNE_NORMAL || ul_style != UL_NONE || strikethrough ||
        overline) {
        Vector_pushv_char(lines, " style=\"", strlen(" style=\""));

        if (style == VT_RUNE_BOLD || style == VT_RUNE_BOLD_ITALIC) {
            Vector_pushv_char(lines, " font-weight: bold;", strlen(" font-weight: bold;"));
        }

        if (style == VT_RUNE_ITALIC || style == VT_RUNE_BOLD_ITALIC) {
            Vector_pushv_char(lines, " font-style: italic;", strlen(" font-style: italic;"));
        }

        if (opt_bg) {
            char* bg_str = asprintf(" background: " COLOR_RGB_FMT ";", COLOR_RGB_AP((*opt_bg)));
            Vector_pushv_char(lines, bg_str, strlen(bg_str));
            free(bg_str);
        }

        if (opt_fg) {
            char* fg_str = asprintf(" color: " COLOR_RGB_FMT ";", COLOR_RGB_AP((*opt_fg)));
            Vector_pushv_char(lines, fg_str, strlen(fg_str));
            free(fg_str);
        }

        if (ul_style != UL_NONE || overline || strikethrough) {
            Vector_pushv_char(lines, " text-decoration: ", strlen(" text-decoration: "));

            switch (ul_style) {
                case UL_NORMAL:
                    Vector_pushv_char(lines, "underline solid", strlen("underline solid"));
                    break;

                case UL_DOUBLE:
                    Vector_pushv_char(lines, "underline double", strlen("underline double"));
                    break;

                case UL_CURLY:
                    Vector_pushv_char(lines, "underline dashed", strlen("underline dashed"));
                    break;

                default:;
            }

            if (strikethrough) {
                Vector_pushv_char(lines, " line-through", strlen(" line-through"));
            }

            if (overline) {
                Vector_pushv_char(lines, " overline", strlen(" overline"));
            }

            ColorRGB* ln_clr = OR(opt_ln, opt_fg);
            if (ln_clr) {
                char buf[64];
                int  len = snprintf(buf, sizeof(buf), " " COLOR_RGB_FMT, COLOR_RGB_AP((*ln_clr)));
                Vector_pushv_char(lines, buf, len);
            }
            Vector_push_char(lines, ';');
        }
        Vector_push_char(lines, '\"');
    }
    Vector_push_char(lines, '>');
}

static void end_span(Vector_char* lines)
{
    Vector_pushv_char(lines, "</span>", strlen("</span>"));
}

static void png_write_to_vec_as_b64_func(void* context, void* data, int size)
{
    if (data) {
        size_t sz  = 0;
        char*  b64 = base64_encode_alloc(data, size, &sz);
        Vector_pushv_char(context, b64, sz);
        free(b64);
    } else {
        WRN("failed to convert sixel image to png\n");
    }
}

void write_html_screen_dump(const Vt* vt, FILE* file)
{
    if (!file || !vt) {
        return;
    }

    static const char* const sixel_css = "\n"
                                         "  .sixel {\n"
                                         "    position: absolute;\n"
                                         "    overflow: hidden;\n"
                                         "  }\n";

    Vector_char sixel_html = Vector_new_char();

    // TODO: fix for line-based sixel rendering
    //for (const RcPtr_VtSixelSurface* i = NULL;
    //     (i = Vector_iter_const_RcPtr_VtSixelSurface(&vt->scrolled_sixels, i));) {

    //    const VtSixelSurface* srf = RcPtr_get_const_VtSixelSurface(i);

    //    if (!srf || !VtSixelSurface_is_visible(vt, srf)) {
    //        continue;
    //    }

    //    int32_t top = srf->anchor_global_index - Vt_top_line(vt), left = srf->anchor_cell_idx,
    //        height = Vt_row(vt) - (top < 0 ? top : 0), width = Vt_col(vt) - (left < 0 ? left : 0);

    //    static const char* sixel_image_begin_format =
    //      "<div z-index=2 class=\"sixel\" style=\"top: %dem; left: %dem height %dem; width: "
    //      "%dem;\"><img src=\"data:image/png;base64,";

    //    char* sixel_image_begin = asprintf(sixel_image_begin_format, top, left, height, width);
    //    Vector_pushv_char(&sixel_html, sixel_image_begin, strlen(sixel_image_begin));
    //    free(sixel_image_begin);

    //    stbi_write_png_compression_level = 9;
    //    stbi_write_png_to_func(png_write_to_vec_as_b64_func,
    //                           &sixel_html,
    //                           srf->width,
    //                           srf->height,
    //                           3,
    //                           srf->fragments.buf,
    //                           0);

    //    static const char* sixel_image_end = "\"></div>";
    //    Vector_pushv_char(&sixel_html, sixel_image_end, strlen(sixel_image_end));
    //}

    static const char* const page_template =
      "<!DOCTYPE html>\n"
      "<html lang=\"en\">\n"
      "\n"
      "<head>\n"
      "  <meta charset=\"UTF-8\">\n"
      "  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n"
      "  <meta name=\"generator\" content=\"" APPLICATION_NAME " v" VERSION "\">\n"
      "  <title>%s</title>\n"
      "</head>\n"
      "\n"
      "<style>\n"
      "  * {\n"
      "    margin: 0;\n"
      "    padding: 0;\n"
      "  }\n"
      "\n"
      "  .blink {\n"
      "    animation: blink-animation 1.0s linear infinite;\n"
      "  }\n"
      "\n"
      "  @keyframes blink-animation {\n"
      "    0%%   { opacity: 0.6; }\n"
      "    10%%  { opacity: 0.1; }\n"
      "    35%%  { opacity: 0.1; }\n"
      "    65%%  { opacity: 1.0; }\n"
      "    90%%  { opacity: 1.0; }\n"
      "    100%% { opacity: 0.6; }\n"
      "  }\n"
      "\n"
      "  #vt {\n"
      "    float: left;\n"
      "    font-family: %s;\n"
      "    font-size: %u;\n"
      "    underline-position: from-font;\n"
      "    background-color: " COLOR_RGB_FMT ";\n"
      "    color: " COLOR_RGB_FMT ";\n"
      "  }\n"
      "%s"
      "</style>\n"
      "\n"
      "<body>\n"
      "  <div id=\"vt\">\n"
      "    <pre>%s</pre>\n"
      "  </div>\n"
      "%s"
      "</body>\n"
      "\n"
      "</html>\n";

    Vector_char lines = Vector_new_with_capacity_char(2048);

    VtLine *begin, *end;
    Vt_get_visible_lines(vt, &begin, &end);

    for (VtLine* line = begin; line < end; ++line) {
        char* spanclass = ((line - begin) % 2) ? "ev" : "od";

        start_span(&lines,
                   spanclass,
                   NULL,
                   NULL,
                   NULL,
                   VT_RUNE_NORMAL,
                   UL_NONE,
                   false,
                   false,
                   false);

        HtmlRuneState old_rune_state = {
            .bg      = ColorRGB_from_RGBA(vt->colors.bg),
            .fg      = vt->colors.fg,
            .ul      = vt->colors.fg,
            .rstyle  = VT_RUNE_NORMAL,
            .ulstyle = UL_NONE,
        };

        int    width;
        size_t line_limit = MIN(Vt_col(vt), line->data.size);

        for (size_t c_idx = 0; c_idx < line_limit; c_idx += MAX(1, width)) {
            VtRune*       rune           = &line->data.buf[c_idx];
            HtmlRuneState new_rune_state = HtmlRuneState_from_vt_rune(vt, rune);

            if (memcmp(&old_rune_state, &new_rune_state, sizeof(new_rune_state))) {
                end_span(&lines);
                start_span(&lines,
                           NULL,
                           VtRune_bg_is_default(rune) ? NULL : &new_rune_state.bg,
                           VtRune_fg_is_default(rune) ? NULL : &new_rune_state.fg,
                           rune->line_color_not_default ? &new_rune_state.ul : NULL,
                           new_rune_state.rstyle,
                           new_rune_state.ulstyle,
                           new_rune_state.strikethrough,
                           new_rune_state.overline,
                           new_rune_state.blink);
            }

            if (rune->hidden || rune->rune.code == ' ' || !rune->rune.code) {
                Vector_push_char(&lines, ' ');
            } else {
                char      buf[5] = { 0 };
                mbstate_t mbs    = { 0 };
                size_t    len    = c32rtomb(buf, rune->rune.code, &mbs);
                if (len > 0) {
                    Vector_pushv_char(&lines, buf, len);
                }
                for (uint_fast8_t i = 0; i < VT_RUNE_MAX_COMBINE && rune->rune.combine[i]; ++i) {
                    memset(&mbs, 0, sizeof(mbs));
                    memset(buf, 0, sizeof(buf));
                    len = c32rtomb(buf, rune->rune.combine[i], &mbs);
                    if (len > 0) {
                        Vector_pushv_char(&lines, buf, len);
                    }
                }
            }
            width          = wcwidth(line->data.buf[c_idx].rune.code);
            old_rune_state = new_rune_state;
        }
        end_span(&lines);

        if (line != end) {
            Vector_push_char(&lines, '\n');
        }
    }

    Vector_push_char(&lines, '\0');
    Vector_push_char(&sixel_html, '\0');

    fprintf(file,
            page_template,
            vt->title,
            settings.styled_fonts.buf[0].family_name,
            settings.font_size,
            COLOR_RGB_AP(vt->colors.bg),
            COLOR_RGB_AP(vt->colors.fg),
            sixel_html.size > 1 ? sixel_css : "",
            lines.buf,
            sixel_html.size > 1 ? sixel_html.buf : "");

    Vector_destroy_char(&lines);
    Vector_destroy_char(&sixel_html);
}
