#include "lightmap.hpp"


void LightValue::add(const LightValue& o) {
    // the contribution of each light adds up linearly
    // http://www.scratchapixel.com/old/lessons/3d-basic-lessons/lesson-15-introduction-to-shading-and-radiometry/light-sources/
    r += o.r;
    g += o.g;
    b += o.b;
}


void LightValue::set(const LightValue& o) {
    r = o.r;
    g = o.g;
    b = o.b;
}


void LightValue::add(const LightValue& o, light_t weight) {
    assert(0.0 <= weight && weight <= 1.0);
    r += o.r * weight;
    g += o.g * weight;
    b += o.b * weight;
}


void LightValue::add(light_t l) {
    r += l;
    g += l;
    b += l;
}


void LightValue::mul(light_t l) {
    assert(0.0 <= l && l <= 1.0);
    r *= l;
    g *= l;
    b *= l;
}


void LightValue::add(Img::rgb_t c, light_t l) {
    r += (c.r / 255.0) * l;
    g += (c.g / 255.0) * l;
    b += (c.b / 255.0) * l;
}


void LightValue::finalize(Img::rgb_t& px) { // undefined state afterwards
    // https://bheisler.github.io/post/writing-raytracer-in-rust-part-2/
    r *= px.r;
    g *= px.g;
    b *= px.b;
    // clamp result, not light value(s)
    px.r = MIN(r, 255.0);
    px.g = MIN(g, 255.0);
    px.b = MIN(b, 255.0);
}


LightMap::LightMap(bool ss, unsigned ww, unsigned hh):
        buf((LightValue*)calloc((ss? 2: 1)*ww*hh, sizeof(LightValue))),
        w(ww), h(hh), sided(ss) {
    assert(w && h);
}


LightMap::~LightMap() {
    free(buf);
}


void LightMap::to_uv(unsigned su, unsigned sv, coord_t& u, coord_t& v) const {
    assert(su < w && sv < h);
    u = ((coord_t)su + 1.0) / ((coord_t)w + 1.0);
    v = ((coord_t)sv + 1.0) / ((coord_t)h + 1.0);
    assert(0.0 <= u && u <= 1.0);
    assert(0.0 <= v && v <= 1.0);
}


LightValue& LightMap::at(side_t s, unsigned x, unsigned y) {
    assert(x < w);
    assert(y < h);
    assert(s == SIDE_A || sided);
    return buf[((unsigned)s*w*h)+(y*w)+(x)];
}


const LightValue& LightMap::at(side_t s, unsigned x, unsigned y) const {
    assert(x < w);
    assert(y < h);
    assert(s == SIDE_A || sided);
    return buf[((unsigned)s*w*h)+(y*w)+(x)];
}


void LightMap::at_uv(side_t s, coord_t u, coord_t v, LightValue& lv) const {
    assert(0.0 <= u && u <= 1.0);
    assert(0.0 <= v && v <= 1.0);

#ifndef LIGHTMAP_INTERPOLATE
    lv.add(at(s, u*(w-1), v*(h-1)));
#else
    u *= w-1;
    v *= h-1;

    coord_t uf = floor(u);
    coord_t uc = ceil(u);
    coord_t vf = floor(v);
    coord_t vc = ceil(v);

    coord_t duc = uc - u;
    coord_t duf = 1.0 - duc;
    coord_t dvc = vc - v;
    coord_t dvf = 1.0 - dvc;

    // TODO: Could trigger a full light raytracing when whe detect a shadow border here
    lv.add(at(s, uf, vf), duc * dvc);
    lv.add(at(s, uf, vc), duc * dvf);
    lv.add(at(s, uc, vf), duf * dvc);
    lv.add(at(s, uc, vc), duf * dvf);
#endif
}