/*  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include <config.h>

#define CHUNK_SIZE 128

#include <stdlib.h>

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

/* A start marked as OPP_START skips the initial checks, since a check
   to the opposing direction has already covered those cells.  */
enum start_type {
  NORMAL, OPP_START
};

struct start_buf {
  enum start_type type;
  Coord loc;
};

typedef struct {
  struct start_buf *start;
  int entries, bufmax;
} Startbuf;

static Startbuf left, right;

static void push_start (const Coord, Startbuf *, const enum
			start_type);
static void track_starts (int *, Startbuf *, enum fill_op (Coord),
			  Coord);
static struct start_buf pop_start (Startbuf *);

extern void
init_fill ()
{
  left.start = my_malloc (CHUNK_SIZE * sizeof (struct start_buf));
  right.start = my_malloc (CHUNK_SIZE * sizeof (struct start_buf));
  left.bufmax = right.bufmax = CHUNK_SIZE;
}

/* FIXME: Segfaults occasionally if reallocs.  Not really sure if it's a
   bug in motti.  Keeping CHUNK_SIZE large avoids this, but it's still a
   bug.  */
static void
push_start (loc, list, type)
     const Coord loc;
     Startbuf *list;
     const enum start_type type;
{
  if (list->entries == list->bufmax)
    {
      list->bufmax += CHUNK_SIZE;
      list->start = realloc (list->start, list->bufmax * sizeof (Coord));
    }
  list->start[list->entries].type = type;
  list->start[list->entries++].loc = 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;
	  push_start (loc, list, NORMAL);
	}
    }
}

/* Return the last entry on the stack.  Decrease list size.  */
static struct start_buf
pop_start (list)
     Startbuf *list;
{
  return list->start[--list->entries];
}

extern enum fill_op
fill (loc, check, mark)
     Coord loc;
     enum fill_op (check) (Coord);
     enum fill_op (mark) (Coord);
{
  enum fill_op ret_status = UNSUCCESSFUL;
  /* Ensures that both directions are checked.  */
  int dir_flip = 1;

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

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

  left.start->loc = loc;
  left.start->type = NORMAL;

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

      /* Array left_scan has the surrounding cells in order:	632
	 Array right_scan has the same order, mirrored.		8.1
								754 */
      const Coord left_scan[8] = {
	{1, 0}, {1, -1}, {0, -1}, {1, 1}, {0, 1}, {-1, -1}, {-1, 1}, {-1, 0}
      };
      const Coord right_scan[8] = {
	{-1, 0}, {-1, -1}, {0, -1}, {-1, 1}, {0, 1}, {1, -1}, {1, 1}, {1, 0}
      };
      const Coord *scan;

      up_start = down_start = 0;

      if (dir_flip)
	{
	  if (left.entries)
	    {
	      scan = left_scan;
	      now_dir = &left;
	      opp_dir = &right;
	      start_info = pop_start (&left);
	    }
	  else if (right.entries)
	    {
	      scan = right_scan;
	      now_dir = &right;
	      opp_dir = &left;
	      start_info = pop_start (&right);
	    }
	  else
	    break;
	  dir_flip = 0;
	}
      else
	{
	  if (right.entries)
	    {
	      scan = right_scan;
	      now_dir = &right;
	      opp_dir = &left;
	      start_info = pop_start (&right);
	    }
	  else if (left.entries)
	    {
	      scan = left_scan;
	      now_dir = &left;
	      opp_dir = &right;
	      start_info = pop_start (&left);
	    }
	  else
	    break;
	  dir_flip = 1;
	}

      check_status = check (start_info.loc);
      if (check_status == SUCCESSFUL)
	{
	  Coord opp_start;
	  opp_start = add_coord (start_info.loc, scan[0]);
	  if (check (opp_start))
	    push_start (opp_start, opp_dir, OPP_START);

	  if (start_info.type == NORMAL)
	    {
	      /* Do initial checks for vertical search.  */
	      track_starts (&up_start, now_dir, check, add_coord
			    (start_info.loc, scan[1]));
	      track_starts (&up_start, now_dir, check, add_coord
			    (start_info.loc, scan[2]));
	      track_starts (&down_start, now_dir, check, add_coord
			    (start_info.loc, scan[3]));
	      track_starts (&down_start, now_dir, check, add_coord
			    (start_info.loc, scan[4]));

	    }

	  do
	    {
	      if (mark (start_info.loc) == SUCCESSFUL && ret_status == 0)
		ret_status = SUCCESSFUL;
	      track_starts (&up_start, now_dir, check, add_coord
			    (start_info.loc, scan[5]));
	      track_starts (&down_start, now_dir, check, add_coord
			    (start_info.loc, scan[6]));
	      start_info.loc = add_coord (start_info.loc, scan[7]);
	      check_status = check (start_info.loc);
	    }
	  while (check_status == SUCCESSFUL);
	}
      if (check_status == BREAK)
	{
	  ret_status = BREAK;
	  break;
	}
    }
  return ret_status;
}
