/*
 * (c) Adam D. Moss : 1998-2000 : adam@gimp.org : adam@foxbox.org
 *
 * GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"


#define PLUG_IN_PROC   "plug-in-the-slimy-egg"
#define PLUG_IN_BINARY "gee"

/* These aren't really redefinable, easily. */
#define IWIDTH  256
#define IHEIGHT 256


/* Declare local functions. */
static void       query (void);
static void       run   (const gchar      *name,
                         gint              nparams,
                         const GimpParam  *param,
                         gint             *nreturn_vals,
                         GimpParam       **return_vals);

static void       do_fun                   (void);

static void       window_response_callback (GtkWidget *widget);
static gboolean   do_iteration             (void);

static void       render_frame             (void);
static void       init_preview_misc        (void);


const GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
};


/* Global widgets'n'stuff */
static guchar     *disp;      /* RGBX preview data      */
static guchar     *env;       /* src warping image data */
static guchar     *bump1base;
static guchar     *bump1;
static guchar     *bump2base;
static guchar     *bump2;
static guchar     *srcbump;
static guchar     *destbump;

static guint       idle_tag;
static GtkWidget  *drawing_area;

static gint32      image_id;
static GimpDrawable      *drawable;
static GimpImageBaseType  imagetype;
static guchar     *palette;
static gint        ncolours;


MAIN ()

static void
query (void)
{
  static const GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,    "run-mode", "Must be interactive (1)" },
    { GIMP_PDB_IMAGE,    "image",    "Input Image"             },
    { GIMP_PDB_DRAWABLE, "drawable", "Input Drawable"          }
  };

  gimp_install_procedure (PLUG_IN_PROC,
                          N_("A big hello from the GIMP team!"),
                          "Beyond help.",
                          "Adam D. Moss <adam@gimp.org>",
                          "Adam D. Moss <adam@gimp.org>",
                          "2000",
                          N_("Gee Slime"),
                          "RGB*, INDEXED*, GRAY*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);
}

static void
run (const gchar      *name,
     gint              n_params,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  static GimpParam  values[1];
  GimpRunMode       run_mode;
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;

  *nreturn_vals = 1;
  *return_vals  = values;

  run_mode = param[0].data.d_int32;

  INIT_I18N ();

  if (run_mode == GIMP_RUN_NONINTERACTIVE ||
      n_params != 3)
    {
      status = GIMP_PDB_CALLING_ERROR;
    }

  if (status == GIMP_PDB_SUCCESS)
    {
      image_id = param[1].data.d_image;
      drawable = gimp_drawable_get (param[2].data.d_drawable);

      if (drawable)
        do_fun ();
      else
        status = GIMP_PDB_CALLING_ERROR;
    }

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
}

static void
build_dialog (void)
{
  GtkWidget *dialog;
  GtkWidget *button;
  GtkWidget *frame;
  gchar     *tmp;

  gimp_ui_init (PLUG_IN_BINARY, TRUE);

  dialog = gimp_dialog_new (_("Gee Slime"), PLUG_IN_BINARY,
                            NULL, 0,
                            gimp_standard_help_func, PLUG_IN_PROC,
                            NULL);

  button = gtk_dialog_add_button (GTK_DIALOG (dialog),
                                  _("Thank You for Choosing GIMP"),
                                  GTK_RESPONSE_OK);

  g_signal_connect (dialog, "response",
                    G_CALLBACK (window_response_callback),
                    NULL);

  tmp = g_strdup_printf (_("A less obsolete creation by %s"),
                         "Adam D. Moss / adam@gimp.org / adam@foxbox.org "
                         "/ 1998-2000");
  gimp_help_set_help_data (button, tmp, NULL);
  g_free (tmp);

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_container_set_border_width (GTK_CONTAINER (frame), 12);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                      frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  drawing_area = gtk_drawing_area_new ();
  gtk_widget_set_size_request (drawing_area, IWIDTH, IHEIGHT);
  gtk_container_add (GTK_CONTAINER (frame), drawing_area);
  gtk_widget_show (drawing_area);

  gtk_widget_show (dialog);

  idle_tag = g_idle_add_full (G_PRIORITY_LOW,
                              (GSourceFunc) do_iteration,
                              NULL,
                              NULL);
}

/* #define LIGHT 0x19
#define LIGHT 0x1a
#define LIGHT 0x21 */
#define LIGHT 0x0d
static guchar llut[256];

static void
gen_llut (void)
{
  int i,k;

  for (i = 0; i < 256; i++)
    {
      k = i + ((LIGHT * ( /* (255*255)- */ i * i)) / (255 * 255));
      k = k + 8;
      k = (k > 255 ? 255 : k);
      llut[i] = k;
    }
}

static void
do_fun (void)
{
  imagetype = gimp_image_base_type (image_id);

  if (imagetype == GIMP_INDEXED)
    palette = gimp_image_get_colormap (image_id, &ncolours);
  else
    if (imagetype == GIMP_GRAY)
      {
        int i;
        palette = g_new (guchar, 256 * 3);
        for (i = 0; i < 256; i++)
          {
            palette[i * 3 + 0] =
            palette[i * 3 + 1] =
            palette[i * 3 + 2] = i;
          }
      }

  /* cache hint */
  gimp_tile_cache_ntiles (1);

  init_preview_misc ();
  build_dialog ();

  gen_llut ();

  render_frame ();

  gtk_main ();
}

static void
show (void)
{
  if (GTK_WIDGET_DRAWABLE (drawing_area))
    {
      GtkStyle *style = gtk_widget_get_style (drawing_area);

      gdk_draw_rgb_32_image (drawing_area->window,
                             style->white_gc,
                             0, 0, IWIDTH, IHEIGHT,
                             GDK_RGB_DITHER_NORMAL,
                             disp, IWIDTH * 4);
    }
}


/* Rendering Functions */

static void
bumpbob (int x, int y, int size)
{
  gint j;

  for (j = 0; j < size; j++)
    {
      int i;
      for (i = 0; i < size; i++)
        {
          int k;
#define BOB_INC 45
          k = destbump[i + x + (y + j) * IWIDTH] + BOB_INC;
          if (k & 256)
            destbump[i + x + (y + j) * IWIDTH] = 255;
          else
            destbump[i + x + (y + j) * IWIDTH] = k;
        }
    }
}

/* Adam's sillier algorithm. */
static void
iterate (void)
{
  static guint frame = 0;

  gint     i, j;
  gint     thisbump;
  guchar   sx, sy;
  guint32 *dest;
  guint32 *environment;
  guchar  *basebump;
  guint    basesx;
  guint    basesy;
  GRand   *gr;
#define bx (-(123-128))
#define by (128+123)
#define cx (by)
#define cy (-bx)
#define bycxmcybx (by*cx-cy*bx)
#define bx2 (((bx)<<19)/bycxmcybx)
#define by2 (((by)<<19)/bycxmcybx)
#define cx2 (((cx)<<19)/bycxmcybx)
#define cy2 (((cy)<<19)/bycxmcybx)

  gr = g_rand_new ();
  frame++;

  environment = (guint32*) env;
  dest = (guint32*) disp;
  srcbump  = (frame & 1) ? bump1 : bump2;
  destbump = (frame & 1) ? bump2 : bump1;

  /* A little sub-pixel jitter to liven things up. */
  basesx = (g_rand_int_range (gr, 0, 29 << 19) / bycxmcybx) +
            ((-128 -((128 * 256) / (cx + bx))) << 11);
  basesy = (g_rand_int_range (gr, 0, 29 << 19) / bycxmcybx) +
            ((-128 - ((128 * 256) / (cy + by))) << 11);

  basebump = srcbump;


  /* MELT DISTORTION MAP, APPLY IT */
  j = IHEIGHT;
  while (j--)
    {
      guint tx;
      guint ty;

      ty = (basesy += cx2);
      tx = (basesx -= bx2);

      i = IWIDTH;
      while (i--)
        {
          guchar *bptr =
            (srcbump + (
                        (
                         ((255 & (
                                  (tx >> 11)
                                 )))
                         |
                         ((((255 & (
                                    (ty >> 11)
                                    )) << 8)))
                         )
                        ));

          thisbump = (11 * *(basebump) + (
                                          *(bptr - IWIDTH) +
                                          *(bptr - 1) +
                                          *(bptr) +
                                          *(bptr + 1) +
                                          *(bptr + IWIDTH)
                                          )
                      );
          basebump++;

          tx += by2;
          ty -= cy2;

          /* TODO: Can accelerate search for non-zero bumps with
             casting an aligned long-word search. */
          if (thisbump == 0)
            {
              *(dest++) = *( environment + (i | (j << 8)));
              *(destbump++) = 0;
            }
          else
            {
              if (thisbump <= (131 << 4))
                {
                  thisbump >>= 4;
                  *destbump = thisbump;
                }
              else
                *destbump = thisbump = 131;

              sy = j + (((thisbump) - *(destbump + IWIDTH)));
              sx = i + (((thisbump) - *(++destbump)));
              *dest++ = *(environment + (sx | (sy << 8)));
            }
        }
    }

  srcbump  = (frame & 1) ? bump1 : bump2;
  destbump = (frame & 1) ? bump2 : bump1;
  dest = (guint32 *) disp;
  memset(destbump, 0, IWIDTH);

#if 1
  /*  CAUSTICS!  */
  /* The idea here is that we refract IWIDTH*IHEIGHT parallel rays
     through the surface of the slime and illuminate the points
     where they hit the backing-image.  There are some unrealistic
     shortcuts taken, but the result is quite pleasing.
  */
  j = IHEIGHT;
  while (j--)
    {
      i = IWIDTH;
      while (i--)
        {
          /* Apply caustics */
          sx = *(destbump++);
          if (sx != 0)
            {
              guchar *cptr;

              sy = j + (((sx) - *(destbump + IWIDTH - 1)));
              sx = i + (((sx) - *(destbump)));

              cptr = (guchar*)(dest + (0xffff ^ (sx | (sy << 8))));

              *cptr = llut[*cptr]; cptr++;
              *cptr = llut[*cptr]; cptr++;
              *cptr = llut[*cptr];
              /* this second point of light's offset (1 across, 1 down)
                 isn't really 'right' but it gives a more pleasing,
                 diffuse look. */
              cptr += 2 + IWIDTH * 4;

              *cptr = llut[*cptr]; cptr++;
              *cptr = llut[*cptr]; cptr++;
              *cptr = llut[*cptr];
            }
        }
    }
#endif


    /* Interactive bumpmap */
#define BOBSIZE 6
#define BOBSPREAD 40
#define BOBS_PER_FRAME 70
  destbump = (frame & 1) ? bump2 : bump1;
  {
    gint            rxp, ryp, posx, posy;
    GdkModifierType mask;
    gint            size, i;

    gdk_window_get_pointer (drawing_area->window, &rxp, &ryp, &mask);

    for (i = 0; i < BOBS_PER_FRAME; i++)
      {
        size = g_rand_int_range (gr, 1, BOBSIZE);

        posx = rxp + BOBSPREAD/2 -
          RINT (sqrt (g_rand_double_range (gr, 0, BOBSPREAD)) *
               g_rand_int_range (gr, 0, BOBSPREAD));
        posy = ryp + BOBSPREAD/2 -
          RINT (sqrt (g_rand_double_range (gr, 0, BOBSPREAD)) *
               g_rand_int_range (gr, 0, BOBSPREAD));

        if (! ((posx < 1) || (posx > IWIDTH-size) ||
               (posy < 1) || (posy > IHEIGHT-size)))
          bumpbob (posx, posy, size);
      }
  }
}

static void
render_frame (void)
{
  iterate ();
  show ();
}

static void
init_preview_misc (void)
{
  GimpPixelRgn pixel_rgn;
  gint         i;
  gboolean     has_alpha;

  has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);

  env = g_new (guchar, 4 * IWIDTH * IHEIGHT * 2);
  disp = g_new (guchar, (IWIDTH + 2 + IWIDTH * IHEIGHT) * 4);
  bump1base = g_new (guchar, IWIDTH * IHEIGHT + IWIDTH + IWIDTH);
  bump2base = g_new (guchar, IWIDTH * IHEIGHT + IWIDTH + IWIDTH);

  bump1 = &bump1base[IWIDTH];
  bump2 = &bump2base[IWIDTH];

  if ((drawable->width < 256) || (drawable->height < 256))
    {
      for (i = 0; i < 256; i++)
        {
          if (i < drawable->height)
            {
              gimp_pixel_rgn_init (&pixel_rgn,
                                   drawable,
                                   drawable->width > 256 ?
                                         drawable->width / 2 - 128 : 0,
                                   (drawable->height > 256 ?
                                         drawable->height / 2 - 128 : 0 ) + i,
                                   MIN (256 , drawable->width),
                                   1,
                                   FALSE,
                                   FALSE);
              gimp_pixel_rgn_get_rect (&pixel_rgn,
                                       &env[(256 * i +
                                             (
                                              (
                                               drawable->width < 256 ?
                                                    (256 - drawable->width) / 2 : 0
                                              )
                                              +
                                              (
                                               drawable->height < 256 ?
                                                    (256 - drawable->height) / 2 : 0
                                               ) * 256
                                              )) *
                                           gimp_drawable_bpp (drawable->drawable_id)
                                       ],
                                       drawable->width > 256 ?
                                            (drawable->width / 2 - 128) : 0,
                                       (drawable->height > 256 ?
                                             (drawable->height / 2 - 128) : 0) + i,
                                       MIN (256, drawable->width),
                                       1);
            }
        }
    }
  else
    {
      gimp_pixel_rgn_init (&pixel_rgn,
                           drawable,
                           drawable->width  > 256 ? (drawable->width  / 2 - 128) : 0,
                           drawable->height > 256 ? (drawable->height / 2 - 128) : 0,
                           MIN (256, drawable->width),
                           MIN (256, drawable->height),
                           FALSE,
                           FALSE);
      gimp_pixel_rgn_get_rect (&pixel_rgn,
                               env,
                               drawable->width  > 256 ? (drawable->width  / 2 - 128) : 0,
                               drawable->height > 256 ? (drawable->height / 2 - 128) : 0,
                               MIN (256, drawable->width),
                               MIN (256, drawable->height));
    }

  gimp_drawable_detach (drawable);

  /* convert the image data of varying types into flat grey or rgb. */
  switch (imagetype)
    {
    case GIMP_GRAY:
    case GIMP_INDEXED:
      if (has_alpha)
        {
          for (i = IWIDTH * IHEIGHT; i > 0; i--)
            {
              env[4 * (i - 1) + 2] =
                ((palette[3 * (env[(i - 1) * 2]) + 2] *
                  env[(i - 1) * 2 + 1]) / 255)
                + ((255 - env[(i - 1) * 2 + 1]) *
                    ((i & 255) ^ (i >> 8))) / 255;
              env[4 * (i - 1) + 1] =
                ((palette[3 * (env[(i - 1) * 2]) + 1] *
                  env[(i - 1) * 2 + 1])/255)
                + ((255 - env[(i - 1) * 2 + 1]) *
                    ((i & 255) ^ (i >> 8))) / 255;
              env[4 * (i - 1) + 0] =
                ((palette[3 * (env[(i - 1) * 2]) + 0]
                  * env[(i - 1) * 2 + 1]) / 255)
                + ((255 - env[(i - 1) * 2 + 1])
                    * ((i & 255) ^ (i >> 8))) / 255;
            }
        }
      else
        {
          for (i = IWIDTH * IHEIGHT; i > 0; i--)
            {
              env[4 * (i - 1) + 2] = palette[3 * (env[i - 1]) + 2];
              env[4 * (i - 1) + 1] = palette[3 * (env[i - 1]) + 1];
              env[4 * (i - 1) + 0] = palette[3 * (env[i - 1]) + 0];
            }
        }
      break;

    case GIMP_RGB:
      if (has_alpha)
        {
          for (i = 0; i < IWIDTH * IHEIGHT; i++)
            {
              env[i * 4 + 2] =
                (env[i * 4 + 2] * env[i * 4 + 3]) / 255
                + ((255 - env[i * 4 + 3]) * ((i & 255) ^ (i >> 8))) / 255;
              env[i * 4 + 1] =
                (env[i * 4 + 1] * env[i * 4 + 3]) / 255
                + ((255 - env[i * 4 + 3]) * ((i & 255) ^ (i >> 8))) / 255;
              env[i * 4 + 0] =
                (env[i * 4 + 0] * env[i * 4 + 3]) / 255
                + ((255 - env[i * 4 + 3]) * ((i & 255) ^ (i >> 8))) / 255;
            }
        }
      else
        {
          for (i = IWIDTH * IHEIGHT; i > 0; i--)
            {
              env[4 * (i - 1) + 2] = env[(i - 1) * 3 + 2];
              env[4 * (i - 1) + 1] = env[(i - 1) * 3 + 1];
              env[4 * (i - 1) + 0] = env[(i - 1) * 3 + 0];
            }
        }
      break;

    default:
      break;
    }

  /* Finally, 180-degree flip the environmental image! */
  for (i = 0; i < IWIDTH * IHEIGHT / 2; i++)
    {
      guchar t;
      t = env[4 * i + 0];
      env[4 * i + 0] = env[4 * (IWIDTH * IHEIGHT - (i + 1)) + 0];
      env[4 * (IWIDTH * IHEIGHT - (i + 1)) + 0] = t;
      t = env[4 * i + 1];
      env[4 * i + 1] = env[4 * (IWIDTH * IHEIGHT - (i + 1)) + 1];
      env[4 * (IWIDTH * IHEIGHT - (i + 1)) + 1] = t;
      t = env[4 * i + 2];
      env[4 * i + 2] = env[4 * (IWIDTH * IHEIGHT - (i + 1)) + 2];
      env[4 * (IWIDTH * IHEIGHT - (i + 1)) + 2] = t;
    }
}

static gboolean
do_iteration (void)
{
  render_frame ();
  show ();

  return TRUE;
}

static void
window_response_callback (GtkWidget *widget)
{
  g_source_remove (idle_tag);
  idle_tag = 0;

  gtk_widget_destroy (widget);
}
