#include "pxl8_lightmap.h"

#include <stdlib.h>
#include <string.h>

pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale) {
    pxl8_lightmap* lm = calloc(1, sizeof(pxl8_lightmap));
    if (!lm) return NULL;

    lm->width = width;
    lm->height = height;
    lm->scale = scale;
    lm->data = calloc(width * height * 3, sizeof(u8));
    if (!lm->data) {
        free(lm);
        return NULL;
    }

    pxl8_lightmap_clear(lm, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL);
    return lm;
}

void pxl8_lightmap_destroy(pxl8_lightmap* lm) {
    if (!lm) return;
    free(lm->data);
    free(lm);
}

void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b) {
    if (!lm || !lm->data) return;
    u32 count = lm->width * lm->height;
    for (u32 i = 0; i < count; i++) {
        lm->data[i * 3 + 0] = r;
        lm->data[i * 3 + 1] = g;
        lm->data[i * 3 + 2] = b;
    }
}

void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b) {
    if (!lm || !lm->data || x >= lm->width || y >= lm->height) return;
    u32 idx = (y * lm->width + x) * 3;
    lm->data[idx + 0] = r;
    lm->data[idx + 1] = g;
    lm->data[idx + 2] = b;
}

void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b) {
    if (!lm || !lm->data || x >= lm->width || y >= lm->height) {
        *r = *g = *b = PXL8_LIGHTMAP_NEUTRAL;
        return;
    }
    u32 idx = (y * lm->width + x) * 3;
    *r = lm->data[idx + 0];
    *g = lm->data[idx + 1];
    *b = lm->data[idx + 2];
}

void pxl8_lightmap_add_point(
    pxl8_lightmap* lm,
    f32 lx, f32 ly,
    u8 r, u8 g, u8 b,
    f32 radius,
    f32 intensity
) {
    if (!lm || !lm->data || radius <= 0.0f) return;

    f32 radius_sq = radius * radius;
    f32 inv_radius_sq = 1.0f / radius_sq;

    i32 cx = (i32)(lx * (f32)lm->width);
    i32 cy = (i32)(ly * (f32)lm->height);
    i32 rad_pixels = (i32)(radius * (f32)lm->width) + 1;

    i32 x0 = cx - rad_pixels;
    i32 y0 = cy - rad_pixels;
    i32 x1 = cx + rad_pixels;
    i32 y1 = cy + rad_pixels;

    if (x0 < 0) x0 = 0;
    if (y0 < 0) y0 = 0;
    if (x1 >= (i32)lm->width) x1 = (i32)lm->width - 1;
    if (y1 >= (i32)lm->height) y1 = (i32)lm->height - 1;

    f32 scale_x = 1.0f / (f32)lm->width;
    f32 scale_y = 1.0f / (f32)lm->height;

    for (i32 y = y0; y <= y1; y++) {
        f32 dy = ((f32)y * scale_y) - ly;
        for (i32 x = x0; x <= x1; x++) {
            f32 dx = ((f32)x * scale_x) - lx;
            f32 dist_sq = dx * dx + dy * dy;

            if (dist_sq >= radius_sq) continue;

            f32 falloff = 1.0f - dist_sq * inv_radius_sq;
            f32 contrib = falloff * falloff * intensity;

            u32 idx = ((u32)y * lm->width + (u32)x) * 3;

            i32 nr = (i32)lm->data[idx + 0] + (i32)((f32)(r - 128) * contrib);
            i32 ng = (i32)lm->data[idx + 1] + (i32)((f32)(g - 128) * contrib);
            i32 nb = (i32)lm->data[idx + 2] + (i32)((f32)(b - 128) * contrib);

            if (nr < 0) nr = 0; if (nr > 255) nr = 255;
            if (ng < 0) ng = 0; if (ng > 255) ng = 255;
            if (nb < 0) nb = 0; if (nb > 255) nb = 255;

            lm->data[idx + 0] = (u8)nr;
            lm->data[idx + 1] = (u8)ng;
            lm->data[idx + 2] = (u8)nb;
        }
    }
}
