#include "colors.hpp"


unsigned char rgb_t::dist(const rgb_t& o) const {
    if (config.dist_func == config_t::DIST_LAB) {
        lab_s a(*this), b(o);
        return a.dist(b) * 255.0 / 400.0;
    } else if (config.dist_func == config_t::DIST_EUCLID) {
        unsigned rr = ABSDIFF(r, o.r);
        unsigned gg = ABSDIFF(g, o.g);
        unsigned bb = ABSDIFF(b, o.b);
        static const double max = std::sqrt(255.0*255.0*3.0);
        return std::sqrt((rr*rr) + (gg*gg) + (bb*bb)) * 255.0 / max;
    } else {
        assert(config.dist_func == config_t::DIST_AVG);
        return ((unsigned)ABSDIFF(r, o.r) + (unsigned)ABSDIFF(g, o.g) + (unsigned)ABSDIFF(b, o.b)) / 3;
    }
}


double rgb_t::fdist(const rgb_t& o) const {
    if (config.dist_func == config_t::DIST_LAB) {
        lab_s a(*this), b(o);
        return a.dist(b);
    } else if (config.dist_func == config_t::DIST_EUCLID) {
        unsigned rr = ABSDIFF(r, o.r);
        unsigned gg = ABSDIFF(g, o.g);
        unsigned bb = ABSDIFF(b, o.b);
        return std::sqrt((rr*rr) + (gg*gg) + (bb*bb));
    } else {
        assert(config.dist_func == config_t::DIST_AVG);
        return ((unsigned)ABSDIFF(r, o.r) + (unsigned)ABSDIFF(g, o.g) + (unsigned)ABSDIFF(b, o.b)) / 3.0;
    }
}


void rgb_t::tint(const rgb_t& col, unsigned char alpha) {
    r = (int)r + ((((int)col.r - (int)r) * (int)alpha) / 100);
    g = (int)g + ((((int)col.g - (int)g) * (int)alpha) / 100);
    b = (int)b + ((((int)col.b - (int)b) * (int)alpha) / 100);
}


lab_s::lab_s(const rgb_t& rgb) {
    struct L { static double cbrt(double v, double vn) {
        v /= vn;
        if (v < 0.009) {
            return ((24389.0/27.0) * v + 16.0) / 116.0;
        } else {
            assert(v > 0.0);
            return std::pow(v, 1/3.0);
        }
    }};

    static const double xn = 94.811;
    static const double yn = 100.0;
    static const double zn = 107.304;
    double x = (0.4124564 * rgb.r) + (0.3575761 * rgb.g) + (0.1804375 * rgb.b);
    double y = (0.2126729 * rgb.r) + (0.7151522 * rgb.g) + (0.0721750 * rgb.b);
    double z = (0.0193339 * rgb.r) + (0.1191920 * rgb.g) + (0.9503041 * rgb.b);
    x = L::cbrt(x, xn);
    y = L::cbrt(y, yn);
    z = L::cbrt(z, zn);
    l = 116.0 * y - 16.0;
    a = 500.0 * (x - y);
    b = 200.0 * (y -z);
}


double lab_t::dist(const lab_t& o) const { // 2.3 corresponds to a just noticeable difference
    struct L { static double diffsq(double a, double b) {
        a -= b;
        return a * a;
    }};
    return std::sqrt(L::diffsq(l, o.l) + L::diffsq(a, o.a) + L::diffsq(b, o.b)); // max should be ~sqrt(100^2 + 270^2 + 250^2)~381
}


/*hsv_u::hsv_u(const rgb_t& rgb) {
    const double min = MIN(MIN(rgb.r, rgb.g), rgb.b);
    const double max = MAX(MAX(rgb.r, rgb.g), rgb.b);
    const double delta = max - min;

    v = (max * 100.0) / 255.0;
    if (delta < 1.0 || max < 1.0) {
        s = 0;
        h = 0;
        return;
    }
    s = (delta / max) * 100.0;

    if (rgb.r == max) {
        h =       (((double)rgb.g - (double)rgb.b) / delta); // scaling needed here?
    } else if (rgb.g == max) {
        h = 2.0 + (((double)rgb.b - (double)rgb.r) / delta);
    } else {
        h = 4.0 + (((double)rgb.r - (double)rgb.g) / delta);
    }

    h *= 60.0;
    if (h < 0.0) {
        h += 360.0;
    }
}
unsigned char hsv_u::dist(const hsv_u& o) const {
    return (ABSDIFF(h, o.h)/360.0) * 255.0; // TODO
}*/