#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()
{
   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;
}
