#include "config.h"
#include <Imlib2.h>

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

#include "filter_common.h"
#include "blend.h"

#define PI (4 * atan(1))

static          Imlib_Image
bump_map(Imlib_Image im, IFunctionParam *par)
{
    Imlib_Image     map = im;
    IFunctionParam *ptr;
    double          an = 0, el = 30, d = 0x200;
    double          red = 0x200, green = 0x200, blue = 0x200;
    double          ambient = 0;

    int             free_map = 0;
    uint32_t       *src;
    uint32_t       *mp, *mpy, *mpp;
    double          z, x2, y2;
    int             w, h, i, j, w2, h2, wh2, mx, my;

    for (ptr = par; ptr; ptr = ptr->next)
    {
        ASSIGN_IMAGE("map", map);
        ASSIGN_INT("angle", an);
        ASSIGN_INT("elevation", el);
        ASSIGN_INT("depth", d);
        ASSIGN_INT("red", red);
        ASSIGN_INT("green", green);
        ASSIGN_INT("blue", blue);
        ASSIGN_INT("ambient", ambient);
    }
    if (!map)
        return im;

    red /= 0x100;
    green /= 0x100;
    blue /= 0x100;
    ambient /= 0x100;
    d /= 0x100;

    imlib_context_set_image(im);
    src = imlib_image_get_data();
    w = imlib_image_get_width();
    h = imlib_image_get_height();

    imlib_context_set_image(map);
    mpp = imlib_image_get_data_for_reading_only();
    w2 = imlib_image_get_width();
    h2 = imlib_image_get_height();
    wh2 = w2 * h2;

    an *= (PI / 180);
    el *= (PI / 180);

    x2 = sin(an) * cos(el);
    y2 = cos(an) * cos(el);
    z = sin(el);

    d /= (255 * (255 + 255 + 255));

    my = h2;
    for (j = h; --j >= 0;)
    {
        mp = mpp;
        mpp += w2;
        if (--my <= 0)
        {
            mpp -= wh2;
            my = h2;
        }
        mpy = mpp;
        mx = w2;
        for (i = w; --i >= 0;)
        {
            double          x1, y1, v;
            int             r, g, b, gr;

            gr = A_VAL(mp) * (R_VAL(mp) + G_VAL(mp) + B_VAL(mp));
            y1 = d * (double)(A_VAL(mpy) * (R_VAL(mpy) +
                                            G_VAL(mpy) + B_VAL(mpy)) - gr);
            mp++;
            mpy++;
            if (--mx <= 0)
            {
                mp -= w2;
                mpy -= w2;
                mx = w2;
            }
            x1 = d * (double)(A_VAL(mp) * (R_VAL(mp) +
                                           G_VAL(mp) + B_VAL(mp)) - gr);
            v = x1 * x2 + y1 * y2 + z;
            v /= sqrt((x1 * x1) + (y1 * y1) + 1.0);
            v += ambient;
            r = v * R_VAL(src) * red;
            g = v * G_VAL(src) * green;
            b = v * B_VAL(src) * blue;
            if (r < 0)
                r = 0;
            if (r > 255)
                r = 255;
            if (g < 0)
                g = 0;
            if (g > 255)
                g = 255;
            if (b < 0)
                b = 0;
            if (b > 255)
                b = 255;
            R_VAL(src) = r;
            G_VAL(src) = g;
            B_VAL(src) = b;

            src++;
        }
    }
    if (free_map)
    {
        imlib_context_set_image(map);
        imlib_free_image();
    }
    return im;
}

static          Imlib_Image
bump_map_point(Imlib_Image im, IFunctionParam *par)
{
    Imlib_Image     map = im;
    IFunctionParam *ptr;
    double          x = 0, y = 0, z = 30, d = 0x200;
    double          red = 0x200, green = 0x200, blue = 0x200;
    double          ambient = 0;

    int             free_map = 0;
    uint32_t       *src;
    uint32_t       *mp, *mpy, *mpp;
    double          z_2, x2, y2;
    int             w, h, i, j, w2, h2, wh2, mx, my;

    for (ptr = par; ptr; ptr = ptr->next)
    {
        ASSIGN_IMAGE("map", map);
        ASSIGN_INT("x", x);
        ASSIGN_INT("y", y);
        ASSIGN_INT("z", z);
        ASSIGN_INT("depth", d);
        ASSIGN_INT("red", red);
        ASSIGN_INT("green", green);
        ASSIGN_INT("blue", blue);
        ASSIGN_INT("ambient", ambient);
    }
    if (!map)
        return im;

    red /= 0x100;
    green /= 0x100;
    blue /= 0x100;
    ambient /= 0x100;
    d /= 0x100;

    imlib_context_set_image(im);
    src = imlib_image_get_data();
    w = imlib_image_get_width();
    h = imlib_image_get_height();

    imlib_context_set_image(map);
    mpp = imlib_image_get_data_for_reading_only();
    w2 = imlib_image_get_width();
    h2 = imlib_image_get_height();
    wh2 = w2 * h2;

    d /= (255 * (255 + 255 + 255));
    z_2 = z * z;

    my = h2;
    y2 = -y;
    for (j = h; --j >= 0;)
    {
        mp = mpp;
        mpp += w2;
        if (--my <= 0)
        {
            mpp -= wh2;
            my = h2;
        }
        mpy = mpp;
        mx = w2;
        x2 = -x;
        for (i = w; --i >= 0;)
        {
            double          x1, y1, v;
            int             r, g, b, gr;

            gr = A_VAL(mp) * (R_VAL(mp) + G_VAL(mp) + B_VAL(mp));
            y1 = d * (double)(A_VAL(mpy) * (R_VAL(mpy) +
                                            G_VAL(mpy) + B_VAL(mpy)) - gr);
            mp++;
            mpy++;
            if (--mx <= 0)
            {
                mp -= w2;
                mpy -= w2;
                mx = w2;
            }
            x1 = d * (double)(A_VAL(mp) * (R_VAL(mp) +
                                           G_VAL(mp) + B_VAL(mp)) - gr);
            v = x1 * x2 + y1 * y2 + z;
            v /= sqrt((x1 * x1) + (y1 * y1) + 1.0);
            v /= sqrt((x2 * x2) + (y2 * y2) + z_2);
            v += ambient;
            r = v * R_VAL(src) * red;
            g = v * G_VAL(src) * green;
            b = v * B_VAL(src) * blue;
            if (r < 0)
                r = 0;
            if (r > 255)
                r = 255;
            if (g < 0)
                g = 0;
            if (g > 255)
                g = 255;
            if (b < 0)
                b = 0;
            if (b > 255)
                b = 255;
            R_VAL(src) = r;
            G_VAL(src) = g;
            B_VAL(src) = b;

            x2++;
            src++;
        }
        y2++;
    }
    if (free_map)
    {
        imlib_context_set_image(map);
        imlib_free_image();
    }
    return im;
}

void
init(ImlibFilterInfo *info)
{
    static const char *const filters[] = { "bump_map_point", "bump_map" };
    int             i = ARRAY_SIZE(filters);

    info->name = strdup("Bump Mapping");
    info->author = strdup("Willem Monsuwe (willem@stack.nl)");
    info->description =
        strdup
        ("Provides bumpmapping to a point and bumpmapping from an infinite light source. *very* cool.");
    info->num_filters = i;
    info->filters = malloc(sizeof(char *) * i);
    while (--i >= 0)
        info->filters[i] = strdup(filters[i]);
}

void
deinit(void)
{
    return;
}

void           *
exec(char *filter, void *im, IFunctionParam *par)
{
    if (!strcmp(filter, "bump_map"))
        return bump_map((Imlib_Image) im, par);
    if (!strcmp(filter, "bump_map_point"))
        return bump_map_point((Imlib_Image) im, par);
    return im;
}
