/* maze.c
 * This is a plug-in for the GIMP.
 * It draws mazes...
 * 
 * Implemented as a GIMP 0.99 Plugin by 
 * Kevin Turner <kevint@poboxes.com>
 * http://www.poboxes.com/kevint/gimp/maze.html
 * 
 * Code generously borrowed from assorted GIMP plugins
 * and used as a template to get me started on this one.  :)
 * 
 * TO DO:
 *   maze_face.c: Rework the divboxes to be more like spinbuttons.
 *
 *   Maybe add an option to kill the outer border.
 * 
 *   Fix that stray line down there between maze wall and dead space border...
 *
 *   handy.c: Make get_colors() work with indexed.  * HELP! *
 *
 */

/*
 * 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.
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef MAZE_DEBUG
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#endif

#include <time.h>  /* For random seeding */

#include <libgimp/gimp.h>

#include "maze.h"

extern gint      maze_dialog (void);
static void      query  (void);
static void      run    (gchar    *name,
			 gint      nparams,
			 GParam   *param,
			 gint     *nreturn_vals,
			 GParam  **return_vals);
static void      maze   (GDrawable * drawable);

static void      mask_maze(gint32 selection_ID, guchar *maz, guint mw, guint mh, 
			   gint x1, gint x2, gint y1, gint y2, gint deadx, gint deady);

/* In algorithms.c */
extern void      mazegen(gint     pos,
			 guchar   *maz,
			 gint     x,
                         gint     y,
			 gint     rnd);
extern void      mazegen_tileable(gint     pos,
				  guchar   *maz,
				  gint     x,
				  gint     y,
				  gint     rnd);
extern void      prim(guint pos,
		      guchar *maz, 
		      guint x, 
		      guint y, 
		      gint rnd);
extern void      prim_tileable(guchar *maz, 
			       guint x, 
			       guint y, 
			       gint rnd);

/* In handy.c */
extern void      get_colors (GDrawable * drawable,
			     guint8 *fg,
			     guint8 *bg);

extern void      drawbox (GPixelRgn *dest_rgn, 
			  guint x, 
			  guint y,
			  guint w,
			  guint h, 
			  guint8 clr[4]);

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

MazeValues mvals = 
{
    /* Calling parameters */
    5,      /* Passage width */
    5,      /* Passage height */
    0,     /* seed */
    FALSE, /* Tileable? */
    57,    /* multiple * These two had "Experiment with this?" comments */
    1,     /* offset   * in the maz.c source, so, lets expiriment.  :) */
    DEPTH_FIRST, /* Algorithm */
    /* Interface options */
    TRUE /* Time seed? */
};

guint sel_w, sel_h;

MAIN () /*;*/

static void
query ()
{
  static GParamDef args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image_ID", "(unused)" },
    { PARAM_DRAWABLE, "drawable_ID", "ID of drawable" },
    /* If we did have parameters, these be them: */
    { PARAM_INT16, "width", "Width of the passages" },
    { PARAM_INT16, "height", "Height of the passages"},
    { PARAM_INT8, "tileable", "Tileable maze?"},
    { PARAM_INT8, "algorithm", "Generation algorithm" 
                              "(0=DEPTH FIRST, 1=PRIM'S ALGORITHM)" },
    { PARAM_INT32, "seed", "Random Seed"},
    { PARAM_INT16, "multiple", "Multiple (use 57)" },
    { PARAM_INT16, "offset", "Offset (use 1)" }
  };
  static GParamDef *return_vals = NULL;
  static int nargs = sizeof (args) / sizeof (args[0]);
  static int nreturn_vals = 0;

  gimp_install_procedure ("plug_in_maze",
			  "Draws a maze.",
			  "Generates a maze using either the depth-first search method or Prim's algorithm.  Can make tileable mazes too.  See " MAZE_URL " for more help.",
			  "Kevin Turner <kevint@poboxes.com>",
			  "Kevin Turner",
			  "1997, 1998",
			  "<Image>/Filters/Render/Maze",
			  "RGB*, GRAY*, INDEXED*",
			  PROC_PLUG_IN,
			  nargs, nreturn_vals,
			  args, return_vals);
}

static void
run    (gchar    *name,
	gint      nparams,
	GParam   *param,
	gint     *nreturn_vals,
	GParam  **return_vals)
{
  static GParam values[1];
  GDrawable *drawable;
  GRunModeType run_mode;
  GStatusType status = STATUS_SUCCESS;
  gint x1, y1, x2, y2;

#ifdef MAZE_DEBUG
  g_print("maze PID: %d\n",getpid());
#endif
  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;

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

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

  switch (run_mode)
    {
    case RUN_INTERACTIVE:
      /* Possibly retrieve data */
      gimp_get_data ("plug_in_maze", &mvals);
      
      /* The interface needs to know the dimensions of the image... */
      gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
      sel_w=x2-x1; sel_h=y2-y1;

      /* Acquire info with a dialog */
      if (! maze_dialog ()) {
	gimp_drawable_detach (drawable);
	return;
      }
      break;
      
    case RUN_NONINTERACTIVE:
      if (nparams != 10)
	{
	  status = STATUS_CALLING_ERROR;
	}
      if (status == STATUS_SUCCESS)
	{
	  mvals.width = (gint16)    param[3].data.d_int16;
	  mvals.height = (gint16)   param[4].data.d_int16;
	  mvals.tile = (gint8)      param[5].data.d_int8;
          mvals.algorithm = (gint8) param[6].data.d_int8;
	  mvals.seed = (gint32)     param[7].data.d_int32;
	  mvals.multiple = (gint16) param[8].data.d_int16;
	  mvals.offset = (gint16)   param[9].data.d_int16;
	}
      break;
    case RUN_WITH_LAST_VALS:
      /* Possibly retrieve data */
      gimp_get_data ("plug_in_maze", &mvals);
      break;
      
    default:
      break;
  }
  
  /* color, gray, or indexed... hmm, miss anything?  ;)  */
  if (gimp_drawable_color (drawable->id) || gimp_drawable_gray (drawable->id) || gimp_drawable_indexed (drawable->id)) {

      maze (drawable);
      
      if (run_mode != RUN_NONINTERACTIVE)
	  gimp_displays_flush ();
      
      if (run_mode == RUN_INTERACTIVE || 
	  (mvals.timeseed && run_mode == RUN_WITH_LAST_VALS))
	  gimp_set_data ("plug_in_maze", &mvals, sizeof (MazeValues));
  } else {
      status = STATUS_EXECUTION_ERROR;
  }

  values[0].data.d_status = status;

  gimp_drawable_detach (drawable);
}

#ifdef MAZE_DEBUG
void
maze_dump(guchar *maz, gint mw, gint mh)
{
     short xx, yy;
     int foo=0;

     for(yy=0;yy<mh;yy++) {
	  for(xx=0;xx<mw;xx++)
	       g_print("%3d ",maz[foo++]);
	  g_print("\n");
     }
}

void
maze_dumpX(guchar *maz, gint mw, gint mh)
{
     short xx, yy;
     int foo=0;

     for(yy=0;yy<mh;yy++) {
	  for(xx=0;xx<mw;xx++)
	       g_print("%c",maz[foo++] ? 'X' : '.');
	  g_print("\n");
     }
}
#endif

static void
maze( GDrawable * drawable)
{
  GPixelRgn dest_rgn;
  guint mw, mh;
  gint deadx, deady;
  guint progress, max_progress;
  gint x1, y1, x2, y2, x, y;
  gint dx, dy, xx, yy;
  gint maz_x, maz_xx, maz_row, maz_yy;
  guint8 fg[4],bg[4];
  gpointer pr;
  gboolean active_selection;

  guchar *maz;
  guint pos;

  /* Gets the input area... */
  active_selection = gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);

  /***************** Maze Stuff Happens Here ***************/

  mw = (x2-x1) / mvals.width;
  mh = (y2-y1) / mvals.height;

  if (!mvals.tile) {
       mw -= !(mw & 1); /* mazegen doesn't work with even-sized mazes. */
       mh -= !(mh & 1); /* Note I don't warn the user about this... */
  } else { /* On the other hand, tileable mazes must be even. */
       mw -= (mw & 1);
       mh -= (mh & 1);
  };

  /* It will really suck if your tileable maze ends up with this dead
     space around it.  Oh well, life is hard. */
  deadx = ((x2-x1) - mw * mvals.width)/2;
  deady = ((y2-y1) - mh * mvals.height)/2;

  maz = g_new0(guchar, mw * mh);

#ifdef MAZE_DEBUG
  printf("x:  %d\ty:  %d\nmw: %d\tmh: %d\ndx: %d\tdy: %d\nwidth:%d\theight: %d\n",
	 (x2-x1),(y2-y1),mw,mh,deadx,deady,mvals.width, mvals.height);
#endif

  if (mvals.timeseed)
       mvals.seed = time(NULL);

  /* Sanity check: */
  switch (mvals.algorithm) {
  case DEPTH_FIRST:
       break;
  case PRIMS_ALGORITHM:
       break;
  default:
       g_warning("maze: Invalid algorithm choice %d", mvals.algorithm);
  }

  if (mvals.tile) {
       switch (mvals.algorithm) {
       case DEPTH_FIRST:
	    mazegen_tileable(0, maz, mw, mh, mvals.seed);
	    break;
       case PRIMS_ALGORITHM:
	    prim_tileable(maz, mw, mh, mvals.seed);
	    break;
       default:
	    ;
       }
  } else { /* not tileable */
       if (active_selection) { /* Mask and draw mazes until there's no 
				* more room left. */
	    mask_maze(drawable->id,
		      maz, mw, mh, x1, x2, y1, y2, deadx, deady);
	    for(maz_yy=mw; maz_yy < (mh*mw); maz_yy += 2*mw) {
		 for(maz_xx=1; maz_xx < mw; maz_xx += 2) {
		      if(maz[maz_yy+maz_xx] == 0) {
			   switch(mvals.algorithm) {
			   case DEPTH_FIRST:
				mazegen(maz_yy+maz_xx, maz, mw, mh, mvals.seed);
				break;
			   case PRIMS_ALGORITHM:
				prim(maz_yy+maz_xx, maz, mw, mh, mvals.seed);
				break;
			   default:
				;
			   } /* switch */
		      } /* if maz[] == 0 */
		 } /* next maz_xx */
	    } /* next maz_yy */
       } else { /* No active selection. */
	    pos=mw+1;
	    switch(mvals.algorithm) {
	    case DEPTH_FIRST:
		 mazegen(pos, maz, mw, mh, mvals.seed);
		 break;
	    case PRIMS_ALGORITHM:
		 prim(pos, maz, mw, mh, mvals.seed);
		 break;
	    default:
		 ;
	    } /* switch */
       } /* no active selection */
  } /* not tileable */

  /************** Begin Drawing *********************/

  /* Initialize pixel region (?) */
  gimp_pixel_rgn_init (&dest_rgn, drawable, x1, y1, (x2 - x1), (y2 - y1), TRUE, TRUE);

  progress = 0;
  max_progress = (x2 - x1) * (y2 - y1);

  /* Get the foreground and background colors */
  get_colors(drawable,fg,bg);

  gimp_progress_init ("Drawing Maze...");

  for (pr = gimp_pixel_rgns_register (1, &dest_rgn); 
       pr != NULL; 
       pr = gimp_pixel_rgns_process (pr))
      {
	  x = dest_rgn.x - x1 - deadx;
	  y = dest_rgn.y - y1 - deady;

	  /* First boxes by edge of tile must be handled specially 
	     because they may have started on a previous tile,
	     unbeknownst to us. */

	  dx = mvals.width - (x % mvals.width);
	  dy = mvals.height - (y % mvals.height);
	  maz_x = x/mvals.width;
	  maz_row = mw * (y/mvals.height);

	  /* Draws the upper-left [split] box */
	  drawbox(&dest_rgn,0,0,dx,dy,
		  (maz[maz_row+maz_x]==IN) ? fg : bg);

	  maz_xx=maz_x+1;
	  /* Draw the top row [split] boxes */
	  for(xx=dx; xx < dest_rgn.w; xx+=mvals.width)
	      { drawbox(&dest_rgn,xx,0,mvals.width,dy,
			(maz[maz_row + maz_xx++]==IN) ? fg : bg ); }

	  maz_yy=maz_row+mw;
	  /* Left column */
	  for(yy=dy; yy < dest_rgn.h; yy+=mvals.height) {
	      drawbox(&dest_rgn,0,yy,dx,mvals.height,
		      (maz[maz_yy + maz_x]==IN) ? fg : bg );
	      maz_yy += mw;
	  }
	  
	  maz_x++;
	  /* Everything else */
	  for(yy=dy; yy < dest_rgn.h; yy+=mvals.height) {
	      maz_xx = maz_x; maz_row+=mw;
	      for(xx=dx; xx < dest_rgn.w; xx+=mvals.width)
		  { 
		      drawbox(&dest_rgn,xx,yy,mvals.width,mvals.height,
			    (maz[maz_row + maz_xx++]==IN) ? fg : bg ); }
	  }

	  progress += dest_rgn.w * dest_rgn.h;
	  gimp_progress_update ((double) progress / (double) max_progress);
	  /* Indicate progress in drawing. */
      }
  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->id, TRUE);
  gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1));
}

/* Shaped mazes: */
/* With
 * Depth first: Nonzero cells will not be connected to or visited.
 * Prim's Algorithm: 
 *  Cells that are not IN will not be connected to.
 *  Cells that are not OUT will not be converted to FRONTIER.
 * 
 * So we'll put unavailable cells in a non-zero non-in non-out class 
 * called MASKED.
 */ 

/* But first...  A little discussion about cells. */

/* In the eyes of the generation algorithms, the world is made up of
 * two sorts of things: Cells, and the walls between them.  Walls can
 * be knocked out, and then you have a passage between cells.  The
 * drawing routine has a simpler view of life...  Everything is a
 * pixel.  Or a block of pixels.  It makes no distinction between
 * passages, walls, and cells.
 *
 *  We may also make the distinction between two different types of
 * passages: horizontal and vertical.  With that in mind, a
 * part of the world looks something like this:
 * 
 * @-@-@-@-  Where @ is a cell, | is a vertical passage, and - is a
 * | | | |   horizontal passsage.
 * @-@-@-@-
 * | | | |   Remember, the maze generation routines will not rest
 *          until the maze is full, that is, every cell is connected
 * to another.  Already, we can determine a few things about the final 
 * maze.  We know which blocks will be cells, which blocks may become
 * passages (and we know what sort), and we also notice that there are
 * some blocks that will never be either cells or passages.
 *
 * Now, back to our masking routine...  To save a little time, lets
 * just take sample points from the block.  We'll sample a point from
 * the top and the bottom of vertical passages, left/right for
 * horizontal, and, hmm, left/right/top/bottom for cells.  And of
 * course, we needn't concern ourselves with the others.  We could
 * also sample the midpoint of each...
 * Then what we'll do is see if the average is higher than some magic 
 * threshold number, and if so, we let maze happen there.  Otherwise
 * we mask it out.
 */

/* And, uh, that's on the TODO list.  Looks like I spent so much time
 * writing comments I haven't left enough to implement the code.  :)
 * Right now we only sample one point. */
static void
mask_maze(gint32 drawable_ID, guchar *maz, guint mw, guint mh, 
	  gint x1, gint x2, gint y1, gint y2, gint deadx, gint deady)
{
     gint32 selection_ID;
     GPixelRgn sel_rgn;  
     gint xx0=0, yy0=0, xoff, yoff;
     guint xx=0, yy=0;
     guint foo=0;

     gint cur_row, cur_col;
     gint x1half, x2half, y1half, y2half;
     guchar *linebuf;

     if ((selection_ID = 
	  gimp_image_get_selection(gimp_drawable_image_id(drawable_ID))) 
	 == -1)
	  return;

     gimp_pixel_rgn_init(&sel_rgn, gimp_drawable_get(selection_ID),
			 x1, y1, (x2-x1), (y2-y1),
			 FALSE, FALSE);
     gimp_drawable_offsets(drawable_ID, &xoff, &yoff);

     /* Special cases:  If mw or mh < 3 */
     /* FIXME (Currently works, but inefficiently.) */
   
     /* mw && mh => 3 */

     linebuf = g_new(guchar, sel_rgn.w * sel_rgn.bpp);

     xx0 = x1 + deadx + mvals.width  + xoff;
     yy0 = y1 + deady + mvals.height + yoff; 

     x1half = mvals.width/2;
     x2half = mvals.width - 1;

     y1half = mvals.height/2;
     y2half = mvals.height - 1;

     /* Here, yy is with respect to the drawable (or something),
        whereas xx is with respect to the row buffer. */

     yy=yy0 + y1half;
     for(cur_row=1; cur_row < mh; cur_row += 2) {

	  gimp_pixel_rgn_get_row(&sel_rgn, linebuf, x1+xoff, yy, (x2-x1));

	  cur_col=1; xx=mvals.width;
	  while(cur_col < mw) {

	       /* Cell: */
	       maz[cur_row * mw + cur_col] = 
		    (linebuf[xx] + linebuf[xx + x1half] + linebuf[xx+x2half]) / 5;

	       cur_col += 1;
	       xx += mvals.width;

	       /* Passage: */
	       if (cur_col < mw) 
		    maz[cur_row * mw + cur_col] =
			 (linebuf[xx] + linebuf[xx + x1half] + linebuf[xx+x2half]) / 3;

	       cur_col += 1;
	       xx += mvals.width;

	  } /* next col */

	  yy += 2 * mvals.height;

     } /* next cur_row += 2 */

     /* Done doing horizontal scans, change this from a row buffer to
        a column buffer. */
     g_free(linebuf);
     linebuf = g_new(guchar, sel_rgn.h * sel_rgn.bpp);

     /* Now xx is with respect to the drawable (or whatever),
        and yy is with respect to the row buffer. */

     xx=xx0 + x1half;
     for(cur_col=1; cur_col < mw; cur_col += 2) {

	  gimp_pixel_rgn_get_col(&sel_rgn, linebuf, xx, y1, (y2-y1));
	  
	  cur_row=1; yy=mvals.height;
	  while(cur_row < mh) {

	       /* Cell: */
	       maz[cur_row * mw + cur_col] +=
		    (linebuf[yy] + linebuf[yy+y2half]) / 5;

	       cur_row += 1;
	       yy += mvals.height;

	       /* Passage: */
	       if (cur_row < mh) 
		    maz[cur_row * mw + cur_col] =
			 (linebuf[yy] + linebuf[yy + y1half] + linebuf[yy+y2half]) / 3;

	       cur_row += 1;
	       yy += mvals.height;
	  } /* next cur_row */

	  xx += 2 * mvals.width;

     } /* next cur_col */

     g_free(linebuf);

     /* Do the alpha -> masked conversion. */

     for(yy=0;yy<mh;yy++) {
	  for(xx=0;xx<mw;xx++) {
	       maz[foo] = ( maz[foo] < MAZE_ALPHA_THRESHOLD ) ? MASKED : OUT;
	       foo++;
	  } /* next xx */
     } /* next yy*/

} /* mask_maze */

/* The attempt to implement this with tiles: (it wasn't fun) */
#if 0

     /* Tiles make my life decidedly difficult here.  There are too
      * many special cases...  "What if a tile starts less/more than
      * halfway through a block?  What if we get a narrow edge-tile
      * that..." etc, etc.  I shall investigate other options.
      * Possibly a row buffer, or can we use something other than this 
      * black-magic gimp_pixel_rgns_register call to get tiles of
      * different sizes?  Now that'd be nice...  */

     for (pr = gimp_pixel_rgns_register (1, &sel_rgn); 
	  pr != NULL; 
	  pr = gimp_pixel_rgns_process (pr)) {

	  /* This gives us coordinates relative to the starting point
	   * of the maze grid.  Negative values happen here if there
	   * is dead space. */
	  x = sel_rgn.x - x1 - deadx;
	  y = sel_rgn.y - y1 - deady;

	  /* These coordinates are relative to the current tile. */
	  /* This starts us off at the first block boundary in the
	   * tile. */

/* e.g. with x=16 and width=10.
 * 16 % 10 = 6
 * 10 - 6 = 4

  x: 6789!123456789!123456789!12
     ....|.........|.........|..
 xx: 0123456789!123456789!123456

 So to start on the boundary, begin at 4.

 For the case x=0, 10-0=10. So xx0 will always between 1 and width. */

	  xx0 = mvals.width  - (x % mvals.width);
	  yy0 = mvals.height - (y % mvals.height);

	  /* Find the corresponding row and column in the maze. */
	  maz_x = (x+xx0)/mvals.width;
	  maz_row = mw * ((y + yy0)/mvals.height);
	  
	  for (yy=yy0*sel_rgn.rowstride;
	       yy < sel_rgn.h*sel_rgn.rowstride;
	       yy+=(mvals.height * sel_rgn.rowstride)) {
	       maz_xx = maz_x;
	       for(xx=xx0*sel_rgn.bpp; 
		   xx < sel_rgn.w; 
		   xx+=mvals.width*sel_rgn.bpp) {
		    if (sel_rgn.data[yy+xx] < MAZE_ALPHA_THRESHOLD)
			 maz[maz_row+maz_xx]=MASKED;
		    maz_xx++;
	       } /* next xx */
	       maz_row+=mw;
	  } /* next yy */	    

     } /* next pr sel_rgn tile thing */
#ifdef MAZE_DEBUG
     /* maze_dump(maz,mw,mh); */
#endif
} /* mask_maze */
#endif /* 0 */
