#include <math.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include "gtk/gtk.h"
#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"
#include "effects.h"


void rgb_to_hsv (double red, double green, double blue,
		 double *hue, double *saturation, double *value)
{
  double h, s, v;
  double min, max;
  double delta;

  h = 0.0; /* Shut up -Wall */

  if (red > green)
    {
      if (red > blue)
        max = red;
      else
        max = blue;

      if (green < blue)
        min = green;
      else
        min = blue;
    }
  else
    {
      if (green > blue)
        max = green;
      else
        max = blue;

      if (red < blue)
        min = red;
      else
        min = blue;
    }

  v = max;

  if (max != 0.0)
    s = (max - min) / max;
  else
    s = 0.0;

  if (s == 0.0)
    {
      h = 0.0;
    }
  else
    {
      delta = max - min;

      if (red == max)
        h = (green - blue) / delta;
      else if (green == max)
        h = 2 + (blue - red) / delta;
      else if (blue == max)
        h = 4 + (red - green) / delta;

      h /= 6.0;

      if (h < 0.0)
        h += 1.0;
      else if (h >= 1.0)
        h -= 1.0;
    }

  *hue        = h;
  *saturation = s;
  *value      = v;
}


void hsv_to_rgb (double hue, double saturation, double value, 
		 double *red, double *green, double *blue)
{
  double f, p, q, t;

  if (saturation == 0.0)
    {
      *red   = value;
      *green = value;
      *blue  = value;
    }
  else
    {
      hue *= 6.0;

      if (hue == 6.0)
        hue = 0.0;

      f = hue - (int) hue;
      p = value * (1.0 - saturation);
      q = value * (1.0 - saturation * f);
      t = value * (1.0 - saturation * (1.0 - f));

      switch ((int) hue)
        {
        case 0:
          *red = value;
          *green = t;
          *blue = p;
          break;

        case 1:
          *red = q;
          *green = value;
          *blue = p;
          break;

        case 2:
          *red = p;
          *green = value;
          *blue = t;
          break;

        case 3:
          *red = p;
          *green = q;
          *blue = value;
          break;
        case 4:
          *red = t;
          *green = p;
          *blue = value;
          break;

        case 5:
          *red = value;
          *green = p;
          *blue = q;
          break;
        }
    }
}

double spread(double x)
{
  return cos(x * M_PI) * -0.5 + 0.5;
}


void rainbow(gdouble *parameters,
	     int width, int height, int bpp,
	     guchar *srcpix, guchar *dstpix)
{
  int pixsize = height * width * bpp;
  int i;

  for (i = 0; i < pixsize; i += bpp)
  {
    guchar r,g,b;
    double red, green, blue;
    double hue, saturation, value;

    r = srcpix[i];
    g = srcpix[i+1];
    b = srcpix[i+2];

    red   = r / 255.0;
    green = g / 255.0;
    blue  = b / 255.0;

    rgb_to_hsv(red, green, blue, &hue, &saturation, &value);

    hue = value * (parameters[1] + 1.0) + parameters[0];
    hue = hue - (int) hue;
    //value = 1.0;
    //saturation = 1.0;

    hsv_to_rgb(hue, saturation, value, &red, &green, &blue);

      
    r = red * 255;
    g = green * 255;
    b = blue * 255;

    dstpix[i]   = r;
    dstpix[i+1] = g;
    dstpix[i+2] = b;
  }

}



void rubbing(gdouble *parameters,
	     int width, int height, int bpp,
	     guchar *srcpix, guchar *dstpix)

     /* the code herein is vastly inefficient.  sue me. */

#define XY(X,Y) (((X) + (Y) * width) * bpp)

{
  guchar *origpix;
  int pixsize = height * width * bpp;
  int x,y;
  int delta;

  if (parameters[0] != 0)
    delta = parameters[0];
  else
    delta = 5;

  for (x = 0; x < width; x++)
    for (y = 0; y < height; y++)
      {
	int x2, y2;

	int count, total, average, orig;
	int color;
	
	total = 0;
	count = 0;

	for (x2 = -delta; x2 <= delta; x2++)
	  for (y2 = -delta; y2 <= delta; y2++)
	    {
	      if (((x + x2) >= 0) &&
		  ((x + x2) < width) &&
		  ((y + y2) >= 0) &&
		  ((y + y2) < height))
		{
		  total += 
		    srcpix[XY(x + x2, y + y2)] + /* red */
		    srcpix[XY(x + x2, y + y2) + 1] + /* green */
		    srcpix[XY(x + x2, y + y2) + 2]; /* blue */
		  count++;
		}
	    }
	
	average = total / count;
	orig = 
	  srcpix[XY(x,y)] +
	  srcpix[XY(x,y)+1] +
	  srcpix[XY(x,y)+2];

	if (average > orig)
	  color = 0;
	else
	color = 255;

	dstpix[XY(x,y)]   = color;
	dstpix[XY(x,y)+1] = color;
	dstpix[XY(x,y)+2] = color;
	
      }

}

typedef struct _rect {
  int left;
  int top;
  int right;
  int bottom;
} rect;

typedef struct _rgb24 {
  unsigned char r;
  unsigned char g;
  unsigned char b;
} rgb24;

typedef struct _surface {
  int width;
  int height;
  int bpp;
  guchar *pix;
} surface;

void
average_rgn(surface *surf, rect *rgn, rgb24 *c)
{
  double r = 0;
  double g = 0;
  double b = 0;
  int i;
  int x, y;
  double blocksize = (rgn->right - rgn->left) * (rgn->bottom - rgn->top);

  /* printf("rgn: (%d, %d) x (%d, %d)\n\r", rgn->left, rgn->top, rgn->right, rgn->bottom); */
  for (y = rgn->top; y < rgn->bottom; y++) {
    i = (y * surf->width * surf->bpp) + rgn->left * surf->bpp;
    for (x = rgn->left; x < rgn->right; x++) {
      r += surf->pix[i++];
      g += surf->pix[i++];
      b += surf->pix[i++];
    }
  }

  /* printf("average_rgn: (%f, %f, %f)\n\r", r, g, b); */   
  c->r = (int)(r / blocksize);
  c->g = (int)(g / blocksize);
  c->b = (int)(b / blocksize);
}

void
average_rgn_circle(surface *surf, rect *rgn, rgb24 *c)
{
  double r = 0;
  double g = 0;
  double b = 0;
  int i;
  int x, y;
  int cx, cy;
  int radius;
  int rsquared;
  int dist;
  double blocksize = (rgn->right - rgn->left) * (rgn->bottom - rgn->top);

  radius = rgn->right - rgn->left;
  rsquared = radius * radius;
  cx = rgn->left + radius;
  cy = rgn->top + radius;
  
  for (y = rgn->top; y < rgn->bottom; y++) {
    i = (y * surf->width * surf->bpp) + rgn->left * surf->bpp;
    for (x = rgn->left; x < rgn->right; x++) {
      r += surf->pix[i++];
      g += surf->pix[i++];
      b += surf->pix[i++];
    }
  }

  /* printf("average_rgn: (%f, %f, %f)\n\r", r, g, b); */   
  c->r = (int)(r / blocksize);
  c->g = (int)(g / blocksize);
  c->b = (int)(b / blocksize);

}


void mosaic(gdouble *parameters,
	    int width, int height, int bpp,
	    guchar *srcpix, guchar *dstpix)
{
  surface surf = { width, height, bpp, srcpix };
  int pixsize = height * width * bpp;
  int step;
  int x, y;
  int x2, y2;
  int i;

  int avgred, avgblue, avggreen;
  rect rgn;
  rgb24 c;
  

  /* First parameter determines the mosaic size */
  if (parameters[0] != 0)
    step = parameters[0];
  else
    step = 5;

  i = 0;

  printf("Gadget: dim (%d, %d) bpp=%d\n\r", width, height, bpp);

  for (y = 0; y < height; y += step) {
    for (x = 0; x < width; x += step) {
      rgn.left = x;
      rgn.top = y;
      rgn.right = x + step;
      rgn.bottom = y + step;
      if (rgn.right >= width) rgn.right = width - 1;
      if (rgn.bottom >= height) rgn.bottom = height - 1;
      average_rgn(&surf, &rgn, &c);
      for (y2 = y; y2 < y + step; y2++) {
	if (y2 >= height) break;
	i = y2 * width * bpp + x * bpp;
	for (x2 = 0; x2 < step; x2++) {
	  if (x2 >= width) break;
	  dstpix[i++] = c.r;
	  dstpix[i++] = c.g;
	  dstpix[i++] = c.b;
	}
      }
    }
  }


#if 0  /* This flips the image vertically */
  i = 0;
  for (y = height - 1; y >= 0; y--) {
    j = y * width * bpp;
    for (x = 0; x < width; x++) {
      dstpix[i++] = srcpix[j++];
      dstpix[i++] = srcpix[j++];
      dstpix[i++] = srcpix[j++];
    }
  }
#endif

}

void
draw_circle(int x, int y, int radius, rgb24 *c, surface *src, surface *dst)
{
  int width = src->width;
  int bpp = src->bpp;
  unsigned char r, g, b;
  
  int i, j;
  int cx, cy;
  int dist;
  int rsquared;
  int idx;
  double scale;

  cx = x + radius;
  cy = y + radius;
  rsquared = radius * radius;
  for (j = y; j < y + radius; j++) {
    for (i = x; i < x + radius; i++) {
      dist = (cx - i) * (cx - i) + (cy - j) * (cy - j);
      /* Inside circle? */
      if (dist < rsquared) {
	scale = (double)((double)sqrt(dist) / (double)sqrt(rsquared));
	scale = 1;
	r = c->r * scale;
	g = c->g * scale;
	b = c->b * scale;

	/* Quadrant II */
	idx = XY(i, j);
	dst->pix[idx] = r;
	dst->pix[idx+1] = g;
	dst->pix[idx+2] = b;

	/* Quadrant I */
	idx = XY(2 * cx - i - 1, j);
	dst->pix[idx] = r;
	dst->pix[idx+1] = g;
	dst->pix[idx+2] = b;

	/* Quadrant III */
	idx = XY(i, 2 * cy - j - 1);
	dst->pix[idx] = r;
	dst->pix[idx+1] = g;
	dst->pix[idx+2] = b;

	/* Quadrant IV */
	idx = XY(2 * cx - i - 1, 2 * cy - j - 1);
	dst->pix[idx] = r;
	dst->pix[idx+1] = g;
	dst->pix[idx+2] = b;

      }
      /*
      else {
	dst->pix[idx] = src->pix[idx];
	dst->pix[idx+1] = src->pix[idx+1];
	dst->pix[idx+2] = src->pix[idx+2];
      }
      */
    }
  }
}

void sniper(gdouble *parameters,
	    int width, int height, int bpp,
	    guchar *srcpix, guchar *dstpix)
{
  surface src = { width, height, bpp, srcpix };
  surface dst = { width, height, bpp, dstpix };
  rgb24 reddish = { 200, 50, 50 }; 
  rect rgn;
  rgb24 c;

  int radius;
  int diameter;

  int b_copy = 0;
  int i;
  int pixsize = height * width * bpp;
  int x, y;

  /* First parameter determines the radius */
  if (parameters[0] != 0)
    radius = parameters[0];
  else
    radius = 5;
  diameter = 2 * radius;

  /* Second parameter is a flag - copy original surface? */
  b_copy = parameters[1];

  /* Copy surface */
  if (b_copy) {
      for (i = 0; i < pixsize; i++) {
	  dstpix[i] = srcpix[i];
      }
  }
  
  for (y = 0; y < height - diameter; y += diameter - 1) {
      for (x = 0; x < width - diameter; x += diameter - 1) {
	  rgn.left = x;
	  rgn.top = y;
	  rgn.right = x + diameter;
	  rgn.bottom = y + diameter;
	  average_rgn(&src, &rgn, &c);
	  draw_circle(x, y, radius, &c, &src, &dst);
      }
  }
}
