/*  Motti -- a strategy game
    Copyright (C) 1999 Free Software Foundation

    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
*/
#define CHUNK_SIZE 128
#define LEFT -1
#define RIGHT 1

#include <stdlib.h>

#include "map.h"
#include "wrappers.h"
#include "fill.h"

typedef struct {
  int used;
  Coord coord;
} Coord_entry;

typedef struct {
  Coord_entry *start;
  Coord_entry *ptr;
  int entries, bufmax;
  int curr_entry;
} Startbuf;

static void add_start (Coord, Startbuf *);
static void track_starts (int *, Startbuf *, enum fill_op (Coord),
			  Coord);

/* TODO: This function is ugly and those pointers suck!  Clean this
   mess, please.  */
static void
add_start (loc, list)
     Coord loc;
     Startbuf *list;
{
  Coord_entry *new_entry;

  if (list->entries++ == list->bufmax)
    {
      if (list->entries % CHUNK_SIZE == 0)
	{
	  int ptr_diff;
	  ptr_diff = (int) ((list->ptr - list->start)
			    * sizeof (Coord_entry));
	  list->start = (Coord_entry *) realloc (list->start,
						 (list->entries /
						  CHUNK_SIZE + 1));
	  list->ptr = (Coord_entry *) ((int) list->start + ptr_diff);
	}
      new_entry = (Coord_entry *) ((int) list->start + list->bufmax++
				   * sizeof (Coord_entry));
    }
  else
    {
      for (new_entry = list->start; new_entry->used; new_entry++);
    }
  new_entry->used = 1;
  new_entry->coord = loc;
}

static void
track_starts (tracking, list, check, loc)
     int *tracking;
     Startbuf *list;
     enum fill_op (check) (Coord);
     Coord loc;
{
  if (*tracking)
    {
      if (!check (loc))
	*tracking = 0;
    }
  else
    {
      if (check (loc))
	{
	  *tracking = 1;
	  add_start (loc, list);
	}
    }
}

extern enum fill_op
fill (loc, check, mark)
     Coord loc;
     enum fill_op (check) (Coord);
     enum fill_op (mark) (Coord);
{
  Startbuf left, right;
  int ret_status = 0;

  /* If the starting location already matches the "check" pattern,
     nothing will be done.  */
  if (!check (loc))
    return 0;

  /* There's got to be at least one "left" entry.  */
  left.entries = left.bufmax = 1;
  right.entries = right.bufmax = 0;
  left.curr_entry = right.curr_entry = 0;

  left.ptr = left.start = (Coord_entry *) my_malloc
    (CHUNK_SIZE * sizeof (Coord_entry));
  right.ptr = right.start = (Coord_entry *) my_malloc
    (CHUNK_SIZE * sizeof (Coord_entry));
  left.start->used = 1;
  left.start->coord = loc;

  while (1)
    {
      int dir;
      Startbuf *now_dir, *opp_dir;
      int up_start, down_start;
      enum fill_op check_status;

      up_start = down_start = 0;

      if (left.entries)
	{
	  dir = LEFT;
	  now_dir = &left;
	  opp_dir = &right;
	}
      else if (right.entries)
	{
	  dir = RIGHT;
	  now_dir = &right;
	  opp_dir = &left;
	}
      else
	{
	  break;
	}

      check_status = check (now_dir->ptr->coord);
      if (check_status == BREAK)
	{
	  ret_status = BREAK;
	  break;
	}
      if (check_status)
	{
	  const Coord scan[8] = {
	    {-dir, 0}, {-dir, -1}, {0, -1}, {-dir, 1}, {0, 1},
	    {dir, -1}, {dir, 1}, {dir, 0}
	  };
	  Coord opp_start;
	  opp_start = add_coord (now_dir->ptr->coord, scan[0]);
	  if (check (opp_start))
	    add_start (opp_start, opp_dir);
	  
	  /* Do initial checks for vertical search.  */
	  track_starts (&up_start, now_dir, check, add_coord
			(now_dir->ptr->coord, scan[1]));
	  track_starts (&up_start, now_dir, check, add_coord
			(now_dir->ptr->coord, scan[2]));
	  track_starts (&down_start, now_dir, check, add_coord
			(now_dir->ptr->coord, scan[3]));
	  track_starts (&down_start, now_dir, check, add_coord
			(now_dir->ptr->coord, scan[4]));

	  do
	    {
	      if (mark (now_dir->ptr->coord) == SUCCESSFUL
		  && ret_status == 0)
		ret_status = 1;
	      track_starts (&up_start, now_dir, check, add_coord
			    (now_dir->ptr->coord, scan[5]));
	      track_starts (&down_start, now_dir, check, add_coord
			    (now_dir->ptr->coord, scan[6]));
	      now_dir->ptr->coord = add_coord (now_dir->ptr->coord,
					       scan[7]);
	      check_status = check (now_dir->ptr->coord);
	    }
	  while (check_status == SUCCESSFUL);
	}
      if (check_status == BREAK)
	{
	  ret_status = BREAK;
	  break;
	}
      /* Free the entry.  */
      now_dir->ptr->used = 0;
      now_dir->entries--;
      /* Search the next entry.  */
      if (now_dir->entries > 0)
	{
	  now_dir->ptr = now_dir->start;
	  while (now_dir->ptr->used == 0)
	    now_dir->ptr++;
	}
    }
  /* Free the memory used by the start tables.  */
  free (left.start);
  free (right.start);
  return ret_status;
}
