/*
   fuse - associative image reconstruction
   Copyright (C) 1997  Scott Draves <spot@cs.cmu.edu>

   The GIMP -- an 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/


/*

  revision history

  Sat Jun 12 1999 - added accelerated matching

  Fri Nov 28 1997 - added template image

  Sun Nov 16 1997 - listbox to select multiple inputs

*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <assert.h>

#include "gtk/gtk.h"
#include "libgimp/gimp.h"
#include "libgimp/gimpmenu.h"
#include "gck/gck.h"


static void query(void);
static void run(char *name,
		int nparams,
		GParam * param,
		int *nreturn_vals,
		GParam ** return_vals);
static gint dialog();

static void doit(GDrawable *);

typedef unsigned long pixel;

typedef struct {
  pixel *pixels;
  int width, height, stride;
} image;


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

int run_flag = 0;


GtkWidget *dlg;
GckListBox *listbox;


#define preview_size 150
GtkWidget *preview;

GDrawable *drawable;

#define max_inputs 100

struct {
  int ninputs;
  gint32 input_image_ids[max_inputs];
  int tile_size;
  int overlap;
  int order;
  int acceleration;
  int search_time;
  int use_template;
  int filter_size;
  double template_weight; /* in [0,10] */
} config = {
  0,
  {0},
  25,
  8,
  0,
  0,
  10,
  0,
  4,
  0.5,
};



MAIN();

static void query()
{
  static GParamDef args[] =
  {
    {PARAM_INT32, "run_mode", "Interactive, non-interactive"},
    {PARAM_IMAGE, "image", "Input image (unused)"},
    {PARAM_DRAWABLE, "drawable", "Input drawable"},
  };
  static GParamDef *return_vals = NULL;
  static int nargs = sizeof(args) / sizeof(args[0]);
  static int nreturn_vals = 0;

  gimp_install_procedure("plug_in_fuse",
			 "associative image reconstruction",
			 "uhm, image dissociation",
			 "Scott Draves",
			 "Scott Draves",
			 "Nov 1997",
			 // "<Image>/Fuse",
			 "<Image>/Filters/Combine/Fuse",
			 "RGB*",
			 PROC_PLUG_IN,
			 nargs, nreturn_vals,
			 args, return_vals);
}

static void
run(char *name, int n_params, GParam * param, int *nreturn_vals,
    GParam ** return_vals)
{
  static GParam values[1];
  GRunModeType run_mode;
  GStatusType status = STATUS_SUCCESS;

  *nreturn_vals = 1;
  *return_vals = values;

  srandom(time(0));

  run_mode = param[0].data.d_int32;
  
  if (run_mode == RUN_NONINTERACTIVE) {
    status = STATUS_CALLING_ERROR;
  } else {
    gimp_get_data("plug_in_fuse", &config);

    drawable = gimp_drawable_get(param[2].data.d_drawable);

    if (run_mode == RUN_INTERACTIVE) {      
      if (!dialog()) {
	status = STATUS_EXECUTION_ERROR;
      }
    }
  }


  if (status == STATUS_SUCCESS) {

    if (gimp_drawable_color(drawable->id)) {
      ;
    } else {
      status = STATUS_EXECUTION_ERROR;
    }
    gimp_drawable_detach(drawable);
  }

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


double
compare(image *in, image *out) {
  int x, y;
  double r = 0.0;
  double t = 0.0;
  for (y = 0; y < in->height; y++) {
    pixel *p = in->pixels + y * in->stride;
    pixel *q = out->pixels + y * out->stride;
    for (x = 0; x < in->width; x++) {
      double rr, z;
      guchar *i = (guchar *) (&p[x]);
      guchar *o = (guchar *) (&q[x]);
      int d;
      if (0 == i[3] || 0 == o[3])
	continue;
      d = i[0] - (int) o[0];
      rr = d * d;
      d = i[1] - (int) o[1];
      rr += d * d;
      d = i[2] - (int) o[2];
      rr += d * d;
      z = i[3] * (double) o[3];
      r += rr * z;
      t += z * z; /* z * 255? */
    }
  }

  if (t < 0.5)
    return -1.0;

  return r/t;
}

void
print_image(char *s, image *i) {
  printf("%s: %x %d %d %d\n",
	 s, (int)i->pixels, i->width, i->height, i->stride);
}

/* apply box filter, treating alpha as denominator (post multiplied).
   clips edges if not 0 mod filter_size.  sets size of destination.
   */

void
filter(image *dst, image *src) {
  int x, y;
  int w, h, c;
  int size = config.filter_size;

  /* the destination is always big enough */
  dst->height = src->height / size;
  dst->width = src->width / size;
  
  for (y = 0; y < dst->height; y++) {
    for (x = 0; x < dst->width; x++) {
      int ov[4];
      for (c = 0; c < 4; c++)
	ov[c] = 0;
      for (h = 0; h < size; h++) {
	pixel *q = src->pixels + (size*y+h) * src->stride;
	for (w = 0; w < size; w++) {
	  guchar *i = (guchar *) (&q[size*x+w]);
	  for (c = 0; c < 4; c++)
	    ov[c] += i[c];
	}
      }
      {
	/* output */
	pixel *p = dst->pixels + y * dst->stride;
	guchar *o = (guchar *) (&p[x]);
	if (ov[3])
	  for (c = 0; c < 4; c++)
	    o[c] = ov[c] / (ov[3] * size * size / 255);
	else
	  for (c = 0; c < 4; c++)
	    o[c] = 0;
      }
    }
  }
}

/* use bbox of dst.  i don't think this should alpha blend */
void
blit(image *dst, image *src) {
  int x, y;
  for (y = 0; y < dst->height; y++) {
    pixel *p = dst->pixels + y * dst->stride;
    pixel *q = src->pixels + y * src->stride;
    for (x = 0; x < dst->width; x++) {
      guchar *o = (guchar *) (&p[x]);
      guchar *i = (guchar *) (&q[x]);
      int s = i[3];
      if (255 == s)
	p[x] = q[x];
      else {
	o[0] = (o[0] * (255 - s) + i[0] * s) >> 8;
	o[1] = (o[1] * (255 - s) + i[1] * s) >> 8;
	o[2] = (o[2] * (255 - s) + i[2] * s) >> 8;
      }
    }
  }
}

/* find tile somewhere in IN that matches OUT and TEMPLATE */
void
source_match(image *best_tile, int *best_x, int *best_y,
	     image *in, image *out, image *template) {
  int i;
  double best_d = HUGE;
  double w = exp(-0.3 * (10-config.template_weight));

  if (0 == out->width || 0 == out->height) {
    fprintf(stderr, "source_match: zero size output\n");
    exit(1);
  }

  for (i = 0; i < 1+(config.search_time<<8); i++) {
    image tile;
    double d;
    int this_x, this_y;

    this_x = (random() % (in->width - out->width));
    this_y = (random() % (in->height - out->height));
    
    tile.pixels = in->pixels + this_x + in->stride * this_y;

    tile.stride = in->stride;
    tile.width = out->width;
    tile.height = out->height;

    d = compare(&tile, out);

    if (d < 0.0) {
      /* should really only happen with null overlap */
      *best_tile = tile;
      *best_x = this_x;
      *best_y = this_y;
      break;
    }
    if (template) {
      double e = compare(&tile, template);
      if (e < 0.0)
	printf("e less than zero\n");
      else
	d = (d * (1.0 - w) + e * w);
    }
      
    if (d < best_d) {
      best_d = d;
      *best_tile = tile;
      *best_x = this_x;
      *best_y = this_y;

    }
  }
}

/* try 4 neighbors to tile and use the best.
   x and y are location of tile in parent.
   returns true if no improvement possible. */
int
improve(image *tile, int x, int y, image *src, image *template, image *parent) {
  double d, best_d;
  image trial;

  best_d = compare(tile, src);


  trial = *tile;

  if (x + 1 + trial.width <= parent->width) {
    trial.pixels += 1;
    if ((d = compare(&trial, src)) < best_d) {
      *tile = trial;
      return 0;
    }
    trial.pixels += -1;
  }

  if (x > 0) {
    trial.pixels += -1;
    if ((d = compare(&trial, src)) < best_d) {
      *tile = trial;
      return 0;
    }
    trial.pixels -= -1;
  }

  if (y + 1 + trial.height <= parent->height) {
    trial.pixels += trial.stride;
    if ((d = compare(&trial, src)) < best_d) {
      *tile = trial;
      return 0;
    }
    trial.pixels -= trial.stride;
  }

  if (y > 0) {
    trial.pixels += -trial.stride;
    if ((d = compare(&trial, src)) < d) {
      *tile = trial;
      return 0;
    }
    trial.pixels -= -trial.stride;
  }
  return 1;
}


void
do_fuse(int nin, image *ins, image *small_ins, image *out, image *template) {
  int i, parity = 0;
  double r, rad;
  double bo;
  int *prmute;
  image small_tile;
  image tile, template_tile;
  image * tplt = template ? &template_tile : NULL;
  int filter_size = config.filter_size;

  if (config.acceleration) {
    int ss = config.tile_size / filter_size;
    int nts = ss * filter_size;
    if (nts != config.tile_size)
      fprintf(stderr, "fuse: truncating tile size from %d to %d.\n",
	      config.tile_size, nts);
    config.tile_size = nts;

    small_tile.width = ss;
    small_tile.height = ss;
    small_tile.stride = ss;
    small_tile.pixels = malloc(sizeof(pixel) * ss * ss);
  }

  bo = config.tile_size - config.overlap;

  for (i = 0; i < nin; i++)
    if (config.tile_size >= ins[i].width ||
	config.tile_size >= ins[i].height) {
      printf("input smaller than tile - bailing\n");
      return;
    }

  rad = sqrt(out->width * out->width +
	     out->height * out->height) / 2;

  prmute = malloc(sizeof(int) * (1 + rad * 2.0 * M_PI / bo));
      
  for (r = 0; r < rad; r += bo) {
    int n = 1 + r * 2.0 * M_PI / bo;

    for (i = 0; i < n; i++)
      prmute[i] = i;
    for (i = 0; i < 2*n; i++) {
      int i1 = random() % n;
      int i2 = random() % n;
      int t = prmute[i1];
      prmute[i1] = prmute[i2];
      prmute[i2] = t;
    }
    parity++;
    for (i = 0; i < n; i++) {
      int x, y, w, h;
      double theta = 2.0 * M_PI * prmute[i] / n;
      if (parity&1)
	theta += M_PI / n;
      w = config.tile_size;
      h = config.tile_size;
      x = (out->width - w) / 2 + r * cos(theta);
      y = (out->height - h) / 2 + r * sin(theta);

      /* clip */
      if (x < 0) {w += x; x = 0;}
      if (y < 0) {h += y; y = 0;}
      if (x > out->width - w) w = out->width - x;
      if (y > out->height - h) h = out->height - y;

      if (w <= 0 || h <= 0)
	continue;

      tile.pixels = out->pixels + y * out->stride + x;
      tile.stride = out->stride;
      tile.width = w;
      tile.height = h;

      if (template) {
	template_tile.pixels = template->pixels + y * template->stride + x;
	template_tile.stride = template->stride;
	template_tile.width = w;
	template_tile.height = h;
      }

      {
	int r = random() % nin;
	image best_tile;
	int best_x, best_y;

	if (config.acceleration) {
	  image big_best_tile;
	  int j;

	  /* search in filtered space but blit in original space */
	  filter(&small_tile, &tile);
	  if (small_tile.width > 0 && small_tile.height > 0) {
	    source_match(&best_tile, &best_x, &best_y,
			 small_ins + r, &small_tile, tplt);

	    big_best_tile.width = best_tile.width * filter_size;
	    big_best_tile.height = best_tile.height * filter_size;
	    big_best_tile.stride = ins[r].stride;
	    big_best_tile.pixels = ins[r].pixels +
	      filter_size * best_y * ins[r].stride +
	      filter_size * best_x;

	    for (j = 0; j < 3*filter_size; j++)
	      if (improve(&big_best_tile, filter_size * best_x, filter_size * best_y,
			  &tile, tplt, &ins[r]))
		break;

	    blit(&tile, &big_best_tile);
	  }

	} else {
	  if (tile.width > 0 && tile.height > 0) {
	    source_match(&best_tile, &best_x, &best_y, ins + r, &tile, tplt);
	    blit(&tile, &best_tile);
	  }
	}
      }

      if ((parity < 4) || ((i&7) == 0)) {
	int xx, yy;
	int cx, cy;
	guchar *buf = (guchar *) malloc(3 * preview_size); /* one row */
	int p2 = preview_size/2;

	if (r < p2) {
	  cx = out->width/2;
	  cy = out->height/2;
	} else {
	  cx = x;
	  cy = y;
	}
	for (yy = cy-p2; yy < cy+p2; yy++) {
	  for (xx = cx-p2; xx < cx+p2; xx++) {
	    guchar *p = &buf[3*(xx-(cx-p2))];
	    guchar *q = (guchar *)(out->pixels+yy*out->stride+xx);
	    if ((xx < out->width) &&
		(xx >= 0) &&
		(yy < out->height) &&
		(yy >= 0)) {
	      p[0] = q[0];
	      p[1] = q[1];
	      p[2] = q[2];
	    } else {
	      p[0] = p[1] = p[2] = 0;
	    }
	  }
	  gtk_preview_draw_row(GTK_PREVIEW (preview),
			       buf, 0, (yy-(cy-p2)), preview_size);
	}
	free(buf);
	gtk_widget_draw (preview, NULL);  
      }
      gimp_progress_update((1-i/(double)n) * r * r / (rad * rad) +
			   i/(double)n * (r + bo) * (r + bo) / (rad * rad));
    }
  }
  free(prmute);
  if (config.acceleration)
    free(small_tile.pixels);
}

static void
doit(GDrawable * target_drawable) {
  image *in, out, template;
  image *small_in;
  gint bytes;
  GPixelRgn pr;
  GDrawable *input_drawable;
  int i;

  if (0 == config.ninputs) {
    fprintf(stderr, "fuse needs at least one input\n");
    return;
  }

  if (config.acceleration && config.use_template) {
    fprintf(stderr, "fuse: accerlation and template are not compatible.\n");
    return;
  }

  in = (image *) malloc(sizeof(image) * config.ninputs);
  small_in = NULL;
  if (config.acceleration)
    small_in = (image *) malloc(sizeof(image) * config.ninputs);

  for (i = 0; i < config.ninputs; i++) {
    image *inp = &in[i];

    inp->width = gimp_image_width(config.input_image_ids[i]);
    inp->height = gimp_image_height(config.input_image_ids[i]);
    inp->stride = inp->width;

    /* allocate and initialize input buffer */

    inp->pixels = (pixel *) malloc(inp->width * inp->height * sizeof(pixel));
    if (inp->pixels == NULL) {
      fprintf(stderr, "cannot malloc %d bytes for input.\n", inp->width * inp->height * 4);
      return;
    }

    {
      int nlayers;
      gint32 *layer_ids;
      layer_ids = gimp_image_get_layers (config.input_image_ids[i], &nlayers);
      if ((NULL == layer_ids) || (nlayers <= 0)) {
	fprintf(stderr, "fuse: error getting layer IDs\n");
	return;
      }
      input_drawable = gimp_drawable_get(layer_ids[nlayers-1]);
      bytes = input_drawable->bpp;

      gimp_pixel_rgn_init(&pr, input_drawable,
			  0, 0, inp->width, inp->height, FALSE, FALSE);
      if (4 == bytes) {
	printf("4 channel image, using alpha\n");
	gimp_pixel_rgn_get_rect(&pr, (guchar *)inp->pixels, 0, 0, inp->width, inp->height);
      } else if (3 == bytes) {
	int i;
	guchar *tb = malloc (inp->width * bytes);
	for (i = 0; i < inp->height; i++) {
	  int j;
	  gimp_pixel_rgn_get_rect(&pr, tb, 0, i, inp->width, 1);
	  for (j = 0; j < inp->width; j++) {
	    guchar *d = (guchar *) (inp->pixels + (inp->width * i) + j);
	    guchar *s = tb + j * bytes;
	    d[0] = s[0];
	    d[1] = s[1];
	    d[2] = s[2];
	    d[3] = 255;
	  }
	}
	free(tb);
      } else if (1 == bytes) {
	int i;
	guchar *tb = malloc (inp->width * bytes);
	for (i = 0; i < inp->height; i++) {
	  int j;
	  gimp_pixel_rgn_get_rect(&pr, tb, 0, i, inp->width, 1);
	  for (j = 0; j < inp->width; j++) {
	    guchar *d = (guchar *) (inp->pixels + (inp->width * i) + j);
	    guchar *s = tb + j * bytes;
	    d[0] = d[1] = d[2] = s[0];
	    d[3] = 255;
	  }
	}
	free(tb);
      } else {
	printf("cannot handle image with %d bytes per pixel\n", bytes);
      }
	

      if (nlayers > 1) {
	input_drawable = gimp_drawable_get(layer_ids[0]);
	bytes = input_drawable->bpp;

	gimp_pixel_rgn_init(&pr, input_drawable,
			    0, 0, inp->width, inp->height, FALSE, FALSE);
	printf("looking for alpha\n");
	if (1 == bytes) {
	  int i;
	  guchar *tb = malloc (inp->width * bytes);
	  printf("got alpha\n");
	  for (i = 0; i < inp->height; i++) {
	    int j;
	    gimp_pixel_rgn_get_rect(&pr, tb, 0, i, inp->width, 1);
	    for (j = 0; j < inp->width; j++) {
	      guchar *d = (guchar *) (inp->pixels + (inp->width * i) + j);
	      guchar *s = tb + j * bytes;
	      d[3] = s[0];
	    }
	  }
	  free(tb);
	}
	
      }
    }
    /* make small version */
    if (config.acceleration) {
      image *sinp = &small_in[i];
      sinp->width = inp->width/config.filter_size;
      sinp->height = inp->height/config.filter_size;
      sinp->stride = sinp->width;
      if (sinp->width == 0 ||
	  sinp->height == 0) {
	fprintf(stderr, "fuse: image smaller than filter size\n");
	return;
      }
      sinp->pixels = (pixel *) malloc(sinp->width * sinp->height * 4);

      filter(sinp, inp);
    }
  }

  {
    out.width = target_drawable->width;
    out.height = target_drawable->height;
    out.stride = out.width;

    out.pixels = (pixel *) malloc(out.width * out.height * 4);
    if (out.pixels == NULL) {
      fprintf(stderr, "cannot malloc %d bytes for output.\n",
	      out.width * out.height * 4);
      return;
    }
  }
  
  if (config.use_template) {
    template.width = target_drawable->width;
    template.height = target_drawable->height;
    template.stride = template.width;
    template.pixels = (pixel *) malloc(template.width * template.height * 4);
    if (template.pixels == NULL) {
      fprintf(stderr, "cannot malloc %d bytes for output.\n",
	      template.width * template.height * 4);
      return;
    }
    
    bytes = target_drawable->bpp;

    gimp_pixel_rgn_init(&pr, target_drawable,
			0, 0, template.width, template.height, FALSE, FALSE);
    if (4 == bytes) {
      printf("4 channel image, using alpha\n");
      gimp_pixel_rgn_get_rect(&pr, (guchar *)template.pixels, 0, 0, template.width, template.height);
    } else if (3 == bytes) {
      int i;
      guchar *tb = malloc (template.width * bytes);
      for (i = 0; i < template.height; i++) {
	int j;
	gimp_pixel_rgn_get_rect(&pr, tb, 0, i, template.width, 1);
	for (j = 0; j < template.width; j++) {
	  guchar *d = (guchar *) (template.pixels + (template.width * i) + j);
	  guchar *s = tb + j * bytes;
	  d[0] = s[0];
	  d[1] = s[1];
	  d[2] = s[2];
	  d[3] = 255;
	}
      }
      free(tb);
    }
  }

  do_fuse(config.ninputs, in, small_in, &out,
	  config.use_template ? &template : NULL);

  gimp_pixel_rgn_init(&pr, target_drawable,
		      0, 0, out.width, out.height, FALSE, FALSE);

  bytes = target_drawable->bpp;


  if (4 == bytes) {
    gimp_pixel_rgn_set_rect(&pr, (guchar *) out.pixels, 0, 0, out.width, out.height);
  } else {
    int i;
    guchar *tb = malloc (out.width * bytes);
    for (i = 0; i < out.height; i++) {
      int j;
      for (j = 0; j < out.width; j++) {
	char *s = (char *) (out.pixels + (out.width * i) + j);
	char *d = tb + j * bytes;
	d[0] = s[0];
	d[1] = s[1];
	d[2] = s[2];
      }
      gimp_pixel_rgn_set_rect(&pr, tb, 0, i, out.width, 1);
    }
    free(tb);
  }
  gimp_drawable_flush(target_drawable);
  /*
  gimp_drawable_merge_shadow(target_drawable->id, TRUE);
  */
  gimp_drawable_update(target_drawable->id, 0, 0,
		       target_drawable->width, target_drawable->height);



  /*
  for (i = 0; i < config.ninputs; i++)
    free(in[i].pixels);
  if (config.acceleration)
    for (i = 0; i < config.ninputs; i++)
      free(small_in[i].pixels);
  free(in);
  
  free(out.pixels);
  if (config.use_template)
    free(template.pixels);
    */
}


static void
close_callback(GtkWidget * widget, gpointer data)
{
  gtk_main_quit();
}

static void
ok_callback(GtkWidget * widget, gpointer data)
{
  GList *sel;
  int n;
  run_flag = 1;

  if (gimp_drawable_color(drawable->id)) {
 
    sel = gck_listbox_get_current_selection(listbox);

    n = 0;
    while (sel != NULL && n < max_inputs) {
    
      config.input_image_ids[n] =
	(gint32)((GckListBoxItem *) sel->data)->user_data;
      sel = sel->next;
      n++;
    }
    config.ninputs = n;

    gimp_progress_init("Fusing...");
    gimp_tile_cache_ntiles(2 * (drawable->width / gimp_tile_width() + 1));

    doit(drawable);

    if (1 == config.ninputs)
      gtk_widget_destroy(dlg);

    gimp_displays_flush();
    gimp_set_data("plug_in_fuse", &config, sizeof(config));
  }
}

static gint
not_indexed_constrain (gint32 image_id, gint32 drawable_id, gpointer data) {

  if ((gimp_image_width (image_id) < config.tile_size)
      && (gimp_image_width (image_id) < config.tile_size))
    return 0;
    
  return INDEXED != gimp_image_base_type (image_id);
}

static void
overlap_callback (GtkWidget *widget, gpointer   data)
{
  config.overlap = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  if (config.overlap > config.tile_size)
    config.overlap = config.tile_size;
}

static void
tile_size_callback (GtkWidget *widget, gpointer   data)
{
  config.tile_size = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  if (config.tile_size < 0)
    config.tile_size = 1;
}

static void
search_time_callback (GtkWidget *widget, gpointer   data)
{
  config.search_time = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  if (config.search_time < 0)
    config.search_time = 0;
}


static void
toggle_callback (GtkWidget *widget, gpointer data)
{
  int *toggle_val;

  toggle_val = (int *) data;

  if (GTK_TOGGLE_BUTTON (widget)->active)
    *toggle_val = TRUE;
  else
    *toggle_val = FALSE;
}

static void
scale_callback (GtkAdjustment *adjustment, gpointer data)
{
  * (double *) data = adjustment->value;
}

static void
filter_size_callback (GtkAdjustment *adjustment, gpointer data)
{
  int size = adjustment->value;
  if (size < 2) size = 2;
  * (int *) data = size;
}

static char*
gimp_base_name (char *str)
{
  char *t;

  t = strrchr (str, '/');
  if (!t)
    return str;
  return t+1;
}

static gint dialog() {
  GtkWidget *frame;
  GtkWidget *button;
  GtkWidget *box;
  gchar **argv;
  gint argc;
  guchar *color_cube;

  argc = 1;
  argv = g_new(gchar *, 1);
  argv[0] = g_strdup("fuse");

  gtk_init(&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  gtk_preview_set_gamma (gimp_gamma ());
  gtk_preview_set_install_cmap (gimp_install_cmap ());
  color_cube = gimp_color_cube ();
  gtk_preview_set_color_cube (color_cube[0], color_cube[1],
			      color_cube[2], color_cube[3]);

  gtk_widget_set_default_visual (gtk_preview_get_visual ());
  gtk_widget_set_default_colormap (gtk_preview_get_cmap ());

  dlg = gtk_dialog_new();
  gtk_window_set_title(GTK_WINDOW(dlg), "Fuse");
  gtk_window_position(GTK_WINDOW(dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect(GTK_OBJECT(dlg), "destroy",
		     (GtkSignalFunc) close_callback, NULL);

  button = gtk_button_new_with_label("Ok");
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  gtk_signal_connect(GTK_OBJECT(button), "clicked",
		     (GtkSignalFunc) ok_callback,
		     dlg);
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  button = gtk_button_new_with_label("Cancel");
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
			    (GtkSignalFunc) gtk_widget_destroy,
			    GTK_OBJECT(dlg));
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  box = GTK_DIALOG(dlg)->vbox;


  {
    gint32 *images;
    int i, k, nimages;
    
    frame = gtk_frame_new("Source Images");
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
    gtk_container_border_width(GTK_CONTAINER(frame), 10);
    gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 10);
    gtk_widget_show(frame);

    listbox = gck_listbox_new(frame, TRUE, TRUE, 10, 150, 150,
			      GTK_SELECTION_MULTIPLE, NULL,
			      NULL /* event_handler */);

    
    images = gimp_query_images (&nimages);
    for (i = 0, k = 0; i < nimages; i++) {
      if (not_indexed_constrain(images[i], 0, 0)) {
	int j;
	GckListBoxItem item;
	char *filename;
	filename = gimp_image_get_filename (images[i]);
	item.label = g_new (char, strlen (filename) + 16);
	sprintf (item.label, "%s-%d", gimp_base_name (filename), images[i]);
	g_free (filename);

	item.widget = NULL;
	item.user_data = (gpointer) images[i];
	item.listbox = NULL;
	gck_listbox_insert_item(listbox, &item, k);
	g_free(item.label);
	for (j = 0; j < config.ninputs; j++)
	  if (images[i] == config.input_image_ids[j])
	    (void) gck_listbox_select_item_by_position(listbox, k);
	k++;
      }
    }
  }

  frame = gtk_frame_new("Parameters");
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width(GTK_CONTAINER(frame), 10);
  gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 10);
  gtk_widget_show(frame);

  box = gtk_vbox_new (FALSE, 5);
  gtk_container_add(GTK_CONTAINER(frame), box);
  gtk_widget_show (box);

  {
    GtkWidget *label;
    GtkWidget *entry;
    GtkWidget *hbox;
    char buffer[20];
    hbox = gtk_hbox_new (FALSE, 5);
    gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);

    label = gtk_label_new ("Tile Size: ");
    gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
    gtk_widget_show (label);

    entry = gtk_entry_new ();
    gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
    sprintf (buffer, "%d", config.tile_size);
    gtk_entry_set_text (GTK_ENTRY (entry), buffer);
    gtk_signal_connect (GTK_OBJECT (entry), "changed",
			(GtkSignalFunc) tile_size_callback,
			NULL);
    gtk_widget_show (entry);

    gtk_widget_show (hbox);
  }


  {
    GtkWidget *label;
    GtkWidget *entry;
    GtkWidget *hbox;
    char buffer[20];
    hbox = gtk_hbox_new (FALSE, 5);
    gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);

    label = gtk_label_new ("Overlap: ");
    gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
    gtk_widget_show (label);

    entry = gtk_entry_new ();
    gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
    sprintf (buffer, "%d", config.overlap);
    gtk_entry_set_text (GTK_ENTRY (entry), buffer);
    gtk_signal_connect (GTK_OBJECT (entry), "changed",
			(GtkSignalFunc) overlap_callback,
			NULL);
    gtk_widget_show (entry);

    gtk_widget_show (hbox);
  }


  {
    GtkWidget *label;
    GtkWidget *entry;
    GtkWidget *hbox;
    char buffer[20];
    hbox = gtk_hbox_new (FALSE, 5);
    gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);

    label = gtk_label_new ("Search Time: ");
    gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
    gtk_widget_show (label);

    entry = gtk_entry_new ();
    gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
    sprintf (buffer, "%d", config.search_time);
    gtk_entry_set_text (GTK_ENTRY (entry), buffer);
    gtk_signal_connect (GTK_OBJECT (entry), "changed",
			(GtkSignalFunc) search_time_callback,
			NULL);
    gtk_widget_show (entry);

    gtk_widget_show (hbox);
  }

  {
    GtkWidget *toggle;
    toggle = gtk_check_button_new_with_label ("use target as template");
    gtk_box_pack_start(GTK_BOX(box), toggle, FALSE, FALSE, 0);
    gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
			(GtkSignalFunc) toggle_callback,
			&config.use_template);
    gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), (config.use_template));
    gtk_widget_show (toggle);
  }

  {
    GtkWidget *label;
    GtkWidget *scale;
    GtkObject *scale_data;
    GtkWidget *hbox;
    hbox = gtk_hbox_new (FALSE, 5);
    gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);

    label = gtk_label_new ("Template weight: ");
    gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
    gtk_widget_show (label);

    scale_data = gtk_adjustment_new (config.template_weight, 0.0, 10.0,
				     0.01, 0.01, 0.0);
    scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
    gtk_widget_set_usize (scale, 150, 0);
    gtk_box_pack_start (GTK_BOX (hbox), scale, TRUE, TRUE, 0);
    gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
    gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
    gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
			(GtkSignalFunc) scale_callback,
			&config.template_weight);
    gtk_widget_show (scale);

    gtk_widget_show (hbox);
  }

  {
    GtkWidget *toggle;
    toggle = gtk_check_button_new_with_label ("accelerated search in filtered space");
    gtk_box_pack_start(GTK_BOX(box), toggle, FALSE, FALSE, 0);
    gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
			(GtkSignalFunc) toggle_callback,
			&config.acceleration);
    gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), (config.acceleration));
    gtk_widget_show (toggle);
  }
  {
    GtkWidget *label;
    GtkWidget *scale;
    GtkObject *scale_data;
    GtkWidget *hbox;
    hbox = gtk_hbox_new (FALSE, 5);
    gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);

    label = gtk_label_new ("Filter Size: ");
    gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
    gtk_widget_show (label);

    scale_data = gtk_adjustment_new (config.filter_size, 2.0, 15.0, 1.0, 1.0, 1.0);
    scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
    gtk_widget_set_usize (scale, 150, 0);
    gtk_box_pack_start (GTK_BOX (hbox), scale, TRUE, TRUE, 0);
    gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
    gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
    gtk_scale_set_digits (GTK_SCALE (scale), 0);
    gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
			(GtkSignalFunc) filter_size_callback,
			&config.filter_size);
    gtk_widget_show (scale);

    gtk_widget_show (hbox);
  }

  box = GTK_DIALOG(dlg)->vbox;

  {
    frame = gtk_frame_new("Preview");
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
    gtk_container_border_width(GTK_CONTAINER(frame), 10);
    gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 10);
    gtk_widget_show(frame);

    
    preview = gtk_preview_new (GTK_PREVIEW_COLOR);
    gtk_preview_size (GTK_PREVIEW (preview), preview_size, preview_size);
    
    gtk_container_add(GTK_CONTAINER(frame), preview);

    gtk_widget_show (preview);
  }

  gtk_widget_show(dlg);
  gtk_main();
  gdk_flush();

  return run_flag;
}
