#include <stdint.h>
#include <stddef.h>

#define INPUT_CAP 65536
#define OUTPUT_CAP (4 * 1024 * 1024)
#define OG_WIDTH 1200
#define OG_HEIGHT 630
#define GLYPH_W 8
#define GLYPH_H 8
#define FONT_SCALE 7
#define EXTRA_LEADING 12
#define PADDING_LEFT 72
#define PADDING_RIGHT 72
#define PADDING_TOP 72
#define PADDING_BOTTOM 72
#define GLYPH_W_PX (GLYPH_W * FONT_SCALE)
#define GLYPH_H_PX (GLYPH_H * FONT_SCALE)
#define ROW_H (GLYPH_H_PX + EXTRA_LEADING)
#define DRAWABLE_W (OG_WIDTH - PADDING_LEFT - PADDING_RIGHT)
#define DRAWABLE_H (OG_HEIGHT - PADDING_TOP - PADDING_BOTTOM)
#define MAX_COLS (DRAWABLE_W / GLYPH_W_PX)
#define MAX_ROWS (DRAWABLE_H / ROW_H)

static unsigned char input_buffer[INPUT_CAP];
static unsigned char output_buffer[OUTPUT_CAP];
static const char output_content_type[] = "image/bmp";
static uint32_t text_color_rgba = 0x000000FFu;       // 0xRRGGBBAA
static uint32_t background_color_rgba = 0xFFFFFFFFu; // 0xRRGGBBAA

__attribute__((export_name("input_ptr")))
uint32_t input_ptr() {
    return (uint32_t)(uintptr_t)input_buffer;
}

__attribute__((export_name("input_utf8_cap")))
uint32_t input_utf8_cap() {
    return INPUT_CAP;
}

__attribute__((export_name("output_ptr")))
uint32_t output_ptr() {
    return (uint32_t)(uintptr_t)output_buffer;
}

__attribute__((export_name("output_bytes_cap")))
uint32_t output_bytes_cap() {
    return OUTPUT_CAP;
}

__attribute__((export_name("output_content_type_ptr")))
uint32_t output_content_type_ptr() {
    return (uint32_t)(uintptr_t)output_content_type;
}

__attribute__((export_name("output_content_type_size")))
uint32_t output_content_type_size() {
    return (uint32_t)(sizeof(output_content_type) - 1);
}

__attribute__((export_name("uniform_set_text_color")))
uint32_t uniform_set_text_color(uint32_t value) {
    text_color_rgba = value;
    return text_color_rgba;
}

__attribute__((export_name("uniform_set_background_color")))
uint32_t uniform_set_background_color(uint32_t value) {
    background_color_rgba = value;
    return background_color_rgba;
}

static void write_u16_le(uint32_t off, uint16_t value) {
    output_buffer[off] = (unsigned char)(value & 0xFF);
    output_buffer[off + 1] = (unsigned char)((value >> 8) & 0xFF);
}

static void write_u32_le(uint32_t off, uint32_t value) {
    output_buffer[off] = (unsigned char)(value & 0xFF);
    output_buffer[off + 1] = (unsigned char)((value >> 8) & 0xFF);
    output_buffer[off + 2] = (unsigned char)((value >> 16) & 0xFF);
    output_buffer[off + 3] = (unsigned char)((value >> 24) & 0xFF);
}

static void set_pixel(uint32_t width, uint32_t height, uint32_t x, uint32_t y,
                      unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
    if (x >= width || y >= height) {
        return;
    }
    uint32_t row = height - 1 - y;
    uint32_t idx = 54 + (row * width + x) * 4;
    output_buffer[idx] = b;
    output_buffer[idx + 1] = g;
    output_buffer[idx + 2] = r;
    output_buffer[idx + 3] = a;
}

static unsigned char rgba_r(uint32_t value) {
    return (unsigned char)((value >> 24) & 0xFFu);
}

static unsigned char rgba_g(uint32_t value) {
    return (unsigned char)((value >> 16) & 0xFFu);
}

static unsigned char rgba_b(uint32_t value) {
    return (unsigned char)((value >> 8) & 0xFFu);
}

static unsigned char rgba_a(uint32_t value) {
    return (unsigned char)(value & 0xFFu);
}

static int is_word_delimiter(unsigned char c) {
    return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}

static uint32_t measure_word_cols(uint32_t input_size, uint32_t start, uint32_t max_cols) {
    uint32_t cols = 0;
    uint32_t i = start;
    while (i < input_size) {
        unsigned char c = input_buffer[i];
        if (is_word_delimiter(c)) {
            break;
        }
        cols++;
        if (cols >= max_cols) {
            break;
        }
        i++;
    }
    return cols;
}

/* Public domain font data from https://github.com/dhepper/font8x8 */
static const unsigned char font8x8_basic[128][8] = {
    { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
    { 0x7E,0x81,0xA5,0x81,0xBD,0x99,0x81,0x7E },
    { 0x7E,0xFF,0xDB,0xFF,0xC3,0xE7,0xFF,0x7E },
    { 0x6C,0xFE,0xFE,0xFE,0x7C,0x38,0x10,0x00 },
    { 0x10,0x38,0x7C,0xFE,0x7C,0x38,0x10,0x00 },
    { 0x38,0x7C,0x38,0xFE,0xFE,0xD6,0x10,0x38 },
    { 0x10,0x38,0x7C,0xFE,0xFE,0x7C,0x10,0x38 },
    { 0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00 },
    { 0xFF,0xFF,0xE7,0xC3,0xC3,0xE7,0xFF,0xFF },
    { 0x00,0x3C,0x66,0x42,0x42,0x66,0x3C,0x00 },
    { 0xFF,0xC3,0x99,0xBD,0xBD,0x99,0xC3,0xFF },
    { 0x0F,0x07,0x0F,0x7D,0xCC,0xCC,0xCC,0x78 },
    { 0x3C,0x66,0x66,0x66,0x3C,0x18,0x7E,0x18 },
    { 0x3F,0x33,0x3F,0x30,0x30,0x70,0xF0,0xE0 },
    { 0x7F,0x63,0x7F,0x63,0x63,0x67,0xE6,0xC0 },
    { 0x99,0x5A,0x3C,0xE7,0xE7,0x3C,0x5A,0x99 },
    { 0x80,0xE0,0xF8,0xFE,0xF8,0xE0,0x80,0x00 },
    { 0x02,0x0E,0x3E,0xFE,0x3E,0x0E,0x02,0x00 },
    { 0x18,0x3C,0x7E,0x18,0x18,0x7E,0x3C,0x18 },
    { 0x66,0x66,0x66,0x66,0x66,0x00,0x66,0x00 },
    { 0x7F,0xDB,0xDB,0x7B,0x1B,0x1B,0x1B,0x00 },
    { 0x3E,0x61,0x3C,0x66,0x66,0x3C,0x86,0x7C },
    { 0x00,0x00,0x00,0x00,0x7E,0x7E,0x7E,0x00 },
    { 0x18,0x3C,0x7E,0x18,0x7E,0x3C,0x18,0xFF },
    { 0x18,0x3C,0x7E,0x18,0x18,0x18,0x18,0x00 },
    { 0x18,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00 },
    { 0x00,0x18,0x0C,0xFE,0x0C,0x18,0x00,0x00 },
    { 0x00,0x30,0x60,0xFE,0x60,0x30,0x00,0x00 },
    { 0x00,0x00,0xC0,0xC0,0xC0,0xFE,0x00,0x00 },
    { 0x00,0x24,0x66,0xFF,0x66,0x24,0x00,0x00 },
    { 0x00,0x18,0x3C,0x7E,0xFF,0xFF,0x00,0x00 },
    { 0x00,0xFF,0xFF,0x7E,0x3C,0x18,0x00,0x00 },
    { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
    { 0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00 },
    { 0x66,0x66,0x66,0x00,0x00,0x00,0x00,0x00 },
    { 0x66,0x66,0xFF,0x66,0xFF,0x66,0x66,0x00 },
    { 0x18,0x3E,0x60,0x3C,0x06,0x7C,0x18,0x00 },
    { 0x00,0xC6,0xCC,0x18,0x30,0x66,0xC6,0x00 },
    { 0x38,0x6C,0x38,0x76,0xDC,0xCC,0x76,0x00 },
    { 0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00 },
    { 0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00 },
    { 0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00 },
    { 0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00 },
    { 0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00 },
    { 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30 },
    { 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00 },
    { 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00 },
    { 0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00 },
    { 0x7C,0xC6,0xCE,0xD6,0xE6,0xC6,0x7C,0x00 },
    { 0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00 },
    { 0x7C,0xC6,0x0E,0x1C,0x70,0xC0,0xFE,0x00 },
    { 0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00 },
    { 0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x1E,0x00 },
    { 0xFE,0xC0,0xFC,0x06,0x06,0xC6,0x7C,0x00 },
    { 0x3C,0x60,0xC0,0xFC,0xC6,0xC6,0x7C,0x00 },
    { 0xFE,0xC6,0x0C,0x18,0x30,0x30,0x30,0x00 },
    { 0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00 },
    { 0x7C,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00 },
    { 0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00 },
    { 0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30 },
    { 0x0E,0x1C,0x38,0x70,0x38,0x1C,0x0E,0x00 },
    { 0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00 },
    { 0x70,0x38,0x1C,0x0E,0x1C,0x38,0x70,0x00 },
    { 0x7C,0xC6,0x0E,0x1C,0x18,0x00,0x18,0x00 },
    { 0x7C,0xC6,0xDE,0xDE,0xDE,0xC0,0x78,0x00 },
    { 0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0x00 },
    { 0xFC,0x66,0x66,0x7C,0x66,0x66,0xFC,0x00 },
    { 0x3C,0x66,0xC0,0xC0,0xC0,0x66,0x3C,0x00 },
    { 0xF8,0x6C,0x66,0x66,0x66,0x6C,0xF8,0x00 },
    { 0xFE,0x62,0x68,0x78,0x68,0x62,0xFE,0x00 },
    { 0xFE,0x62,0x68,0x78,0x68,0x60,0xF0,0x00 },
    { 0x3C,0x66,0xC0,0xC0,0xCE,0x66,0x3E,0x00 },
    { 0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0x00 },
    { 0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00 },
    { 0x1E,0x0C,0x0C,0x0C,0xCC,0xCC,0x78,0x00 },
    { 0xE6,0x66,0x6C,0x78,0x6C,0x66,0xE6,0x00 },
    { 0xF0,0x60,0x60,0x60,0x62,0x66,0xFE,0x00 },
    { 0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0x00 },
    { 0xC6,0xE6,0xF6,0xDE,0xCE,0xC6,0xC6,0x00 },
    { 0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00 },
    { 0xFC,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00 },
    { 0x7C,0xC6,0xC6,0xC6,0xD6,0xCC,0x7A,0x00 },
    { 0xFC,0x66,0x66,0x7C,0x6C,0x66,0xE6,0x00 },
    { 0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00 },
    { 0x7E,0x7E,0x5A,0x18,0x18,0x18,0x3C,0x00 },
    { 0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00 },
    { 0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00 },
    { 0xC6,0xC6,0xC6,0xD6,0xFE,0xEE,0xC6,0x00 },
    { 0xC6,0xC6,0x6C,0x38,0x38,0x6C,0xC6,0x00 },
    { 0x66,0x66,0x66,0x3C,0x18,0x18,0x3C,0x00 },
    { 0xFE,0xC6,0x8C,0x18,0x32,0x66,0xFE,0x00 },
    { 0x3C,0x30,0x30,0x30,0x30,0x30,0x3C,0x00 },
    { 0xC0,0x60,0x30,0x18,0x0C,0x06,0x02,0x00 },
    { 0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00 },
    { 0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00 },
    { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF },
    { 0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00 },
    { 0x00,0x00,0x7C,0x06,0x7E,0xC6,0x7E,0x00 },
    { 0xE0,0x60,0x7C,0x66,0x66,0x66,0xDC,0x00 },
    { 0x00,0x00,0x7C,0xC6,0xC0,0xC6,0x7C,0x00 },
    { 0x1C,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00 },
    { 0x00,0x00,0x7C,0xC6,0xFE,0xC0,0x7C,0x00 },
    { 0x3C,0x66,0x60,0xF8,0x60,0x60,0xF0,0x00 },
    { 0x00,0x00,0x76,0xCC,0xCC,0x7C,0x0C,0xF8 },
    { 0xE0,0x60,0x6C,0x76,0x66,0x66,0xE6,0x00 },
    { 0x18,0x00,0x38,0x18,0x18,0x18,0x3C,0x00 },
    { 0x0C,0x00,0x1C,0x0C,0x0C,0xCC,0xCC,0x78 },
    { 0xE0,0x60,0x66,0x6C,0x78,0x6C,0xE6,0x00 },
    { 0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00 },
    { 0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xC6,0x00 },
    { 0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x00 },
    { 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0x00 },
    { 0x00,0x00,0xDC,0x66,0x66,0x7C,0x60,0xF0 },
    { 0x00,0x00,0x76,0xCC,0xCC,0x7C,0x0C,0x1E },
    { 0x00,0x00,0xDC,0x76,0x66,0x60,0xF0,0x00 },
    { 0x00,0x00,0x7E,0xC0,0x7C,0x06,0xFC,0x00 },
    { 0x30,0x30,0xFC,0x30,0x30,0x36,0x1C,0x00 },
    { 0x00,0x00,0xCC,0xCC,0xCC,0xCC,0x76,0x00 },
    { 0x00,0x00,0xC6,0xC6,0xC6,0x6C,0x38,0x00 },
    { 0x00,0x00,0xC6,0xD6,0xD6,0xFE,0x6C,0x00 },
    { 0x00,0x00,0xC6,0x6C,0x38,0x6C,0xC6,0x00 },
    { 0x00,0x00,0xC6,0xC6,0xC6,0x7E,0x06,0xFC },
    { 0x00,0x00,0xFE,0x4C,0x18,0x32,0xFE,0x00 },
    { 0x0E,0x18,0x18,0x70,0x18,0x18,0x0E,0x00 },
    { 0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00 },
    { 0x70,0x18,0x18,0x0E,0x18,0x18,0x70,0x00 },
    { 0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00 },
    { 0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0x00 }
};

static uint32_t count_rows(uint32_t input_size, uint32_t max_cols, uint32_t max_rows) {
    uint32_t rows = 1;
    uint32_t col = 0;
    uint32_t i = 0;
    while (i < input_size) {
        unsigned char c = input_buffer[i];
        if (c == '\r') {
            if (i + 1 < input_size && input_buffer[i + 1] == '\n') {
                i++;
            }
            rows++;
            if (rows >= max_rows) {
                return max_rows;
            }
            col = 0;
            i++;
            continue;
        }
        if (c == '\n') {
            rows++;
            if (rows >= max_rows) {
                return max_rows;
            }
            col = 0;
            i++;
            continue;
        }
        if (c == '\t') {
            uint32_t spaces = 4 - (col % 4);
            col += spaces;
            while (col >= max_cols) {
                rows++;
                if (rows >= max_rows) {
                    return max_rows;
                }
                col = 0;
            }
            i++;
            continue;
        }

        if (c != ' ') {
            int at_word_start = (i == 0) || is_word_delimiter(input_buffer[i - 1]);
            if (at_word_start && col > 0) {
                uint32_t word_cols = measure_word_cols(input_size, i, max_cols);
                if (word_cols <= max_cols && col + word_cols > max_cols) {
                    rows++;
                    if (rows >= max_rows) {
                        return max_rows;
                    }
                    col = 0;
                }
            }
        }

        col++;
        if (col >= max_cols) {
            rows++;
            if (rows >= max_rows) {
                return max_rows;
            }
            col = 0;
        }
        i++;
    }
    return rows;
}

static void draw_glyph_scaled(uint32_t base_x, uint32_t base_y, unsigned char glyph,
                              unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
    for (uint32_t gy = 0; gy < GLYPH_H; gy++) {
        unsigned char bits = font8x8_basic[glyph][gy];
        for (uint32_t gx = 0; gx < GLYPH_W; gx++) {
            if ((bits & (1u << (7u - gx))) == 0) {
                continue;
            }
            uint32_t px0 = base_x + gx * FONT_SCALE;
            uint32_t py0 = base_y + gy * FONT_SCALE;
            for (uint32_t sy = 0; sy < FONT_SCALE; sy++) {
                for (uint32_t sx = 0; sx < FONT_SCALE; sx++) {
                    set_pixel(OG_WIDTH, OG_HEIGHT, px0 + sx, py0 + sy, r, g, b, a);
                }
            }
        }
    }
}

__attribute__((export_name("run")))
uint32_t run(uint32_t input_size) {
    if (input_size > INPUT_CAP) {
        input_size = INPUT_CAP;
    }

    const uint32_t width = OG_WIDTH;
    const uint32_t height = OG_HEIGHT;
    const uint32_t max_cols = MAX_COLS;
    const uint32_t max_rows = MAX_ROWS;
    if (max_cols == 0 || max_rows == 0) {
        return 0;
    }

    uint32_t rows = count_rows(input_size, max_cols, max_rows);
    uint64_t pixel_bytes = (uint64_t)width * (uint64_t)height * 4u;
    uint64_t total = 54u + pixel_bytes;
    if (total > OUTPUT_CAP) {
        return 0;
    }

    unsigned char bg_r = rgba_r(background_color_rgba);
    unsigned char bg_g = rgba_g(background_color_rgba);
    unsigned char bg_b = rgba_b(background_color_rgba);
    unsigned char bg_a = rgba_a(background_color_rgba);
    unsigned char fg_r = rgba_r(text_color_rgba);
    unsigned char fg_g = rgba_g(text_color_rgba);
    unsigned char fg_b = rgba_b(text_color_rgba);
    unsigned char fg_a = rgba_a(text_color_rgba);

    for (uint32_t i = 0; i < 54; i++) {
        output_buffer[i] = 0;
    }
    for (uint64_t off = 54; off < total; off += 4) {
        output_buffer[off] = bg_b;
        output_buffer[off + 1] = bg_g;
        output_buffer[off + 2] = bg_r;
        output_buffer[off + 3] = bg_a;
    }

    // BMP header
    output_buffer[0] = 'B';
    output_buffer[1] = 'M';
    write_u32_le(2, (uint32_t)total);
    write_u32_le(6, 0);
    write_u32_le(10, 54);
    write_u32_le(14, 40);
    write_u32_le(18, width);
    write_u32_le(22, height);
    write_u16_le(26, 1);
    write_u16_le(28, 32);
    write_u32_le(30, 0);
    write_u32_le(34, (uint32_t)pixel_bytes);
    write_u32_le(38, 2835);
    write_u32_le(42, 2835);
    write_u32_le(46, 0);
    write_u32_le(50, 0);

    uint32_t col = 0;
    uint32_t row = 0;
    uint32_t i = 0;
    while (i < input_size && row < rows && row < max_rows) {
        unsigned char c = input_buffer[i];
        if (c == '\r') {
            if (i + 1 < input_size && input_buffer[i + 1] == '\n') {
                i++;
            }
            row++;
            col = 0;
            i++;
            continue;
        }
        if (c == '\n') {
            row++;
            col = 0;
            i++;
            continue;
        }
        if (c == '\t') {
            uint32_t spaces = 4 - (col % 4);
            for (uint32_t s = 0; s < spaces; s++) {
                col++;
                if (col >= max_cols) {
                    row++;
                    col = 0;
                    if (row >= rows) break;
                }
            }
            i++;
            continue;
        }

        if (c != ' ') {
            int at_word_start = (i == 0) || is_word_delimiter(input_buffer[i - 1]);
            if (at_word_start && col > 0) {
                uint32_t word_cols = measure_word_cols(input_size, i, max_cols);
                if (word_cols <= max_cols && col + word_cols > max_cols) {
                    row++;
                    col = 0;
                    if (row >= rows || row >= max_rows) {
                        break;
                    }
                }
            }
        }

        unsigned char glyph = c;
        if (glyph < 32 || glyph >= 128) {
            glyph = '?';
        }

        uint32_t base_x = PADDING_LEFT + col * GLYPH_W_PX;
        uint32_t base_y = PADDING_TOP + row * ROW_H;
        draw_glyph_scaled(base_x, base_y, glyph, fg_r, fg_g, fg_b, fg_a);

        col++;
        if (col >= max_cols) {
            row++;
            col = 0;
        }
        i++;
    }

    return (uint32_t)total;
}
