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

/* This file has everything, that happens when a cell is occupied.
*/
enum connect_type {
  UNCONNECTED,
  CONNECTED,
  BORDER,
  PASSED,
  NOT_ACCOUNTED,
  IGNORED
};

#include <stdlib.h>

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

static enum fill_op borders (Coord);
static enum fill_op mark_connected (Coord);
static enum fill_op blocked (Coord);
static enum fill_op mark_passed (Coord);
static enum fill_op not_connected (Coord);
static enum fill_op mark_border (Coord);
static int search_encircled ();
static int fill_check (void);
static int deadlock (Coord, int, int *, unsigned char *,
		     void (*) (Coord, int, int *));
static void count_connects (Coord, int, int *);
static void comp_inv_dist (Coord, int, int *);
static void invade (Coord);
static void occupy (Action *, int *);
static enum fill_op search_contact (Coord);
static void loop_by_bit_mask (char *, int);
static int disable_player (void);
static int new_turn (void);

static enum connect_type *connect = NULL;
static int player;

extern void
init_occupy ()
{
  connect = my_malloc (sizeof (enum connect_type) * game_map.size);
}

extern void
defeat_players (pl_mask, act)
     const int pl_mask;
     Action *act;
{
  register int i, j;
  int have_defeats = 0, need_turnchange = 0;

  act->type = 0;
  act->count = 0;
  if (!pl_mask)
    return;
  for (j = 0; j < game_map.size; j++)
    connect[j] = CONNECTED;

  for (i = 1; i <= game_map.players; i++)
    if (1<<(i-1) & pl_mask)
      {
	int mask = ~(1<<(i-1));

	if (game_map.turn == i)
	  need_turnchange = 1;

	game_map.active_players &= mask;
	game_map.remaining_players &= mask;
	game_map.n_active_players--;
	game_map.players_left--;
	for (j = 0; j < game_map.size; j++)
	  if ((game_map.map[j] & MASK_PLAYER) == i)
	    connect[j] = UNCONNECTED;
      }
  for (i = 0; i < game_map.size; i++)
    if (game_map.map[i] == SEA_VAL)
      connect[i] = IGNORED;

  if (game_map.remaining_players > 1)
    {
      player = 0;
      while (search_encircled ());
      if (need_turnchange)
	{
	  if (new_turn ())
	    act->type = EVENT_NEWTURN|EVENT_UNOCCUPY|EVENT_DEFEAT;
	  else
	    act->type = EVENT_NEWTURN|EVENT_DEFEAT;
	  return;
	}
    }
  act->type = EVENT_DEFEAT;
}

/* Mark all other players' occupied cells, which are next to any cells passed
   on the first pass.  This leaves islands of other players' areas inside the
   encircled player's encircled area unaccounted.  */
static enum fill_op
borders (loc)
     Coord loc;
{
  get_map_real_coord (&loc);
  if (loc.x != -1)
    {
      register int ref = parse_coord (loc);
      /* Ok from the first pass.  */
      if (connect[ref] == PASSED
	  /* Not yet passed on the second pass.  */
	  || ((connect[ref] == UNCONNECTED || connect[ref] == BORDER)
	      /* Other player's cell.  */
	      && (game_map.map[ref] & MASK_PLAYER) != player))
	return SUCCESSFUL;
    }
  return UNSUCCESSFUL;
}

static enum fill_op
mark_connected (loc)
     Coord loc;
{
  register int ref = parse_coord (loc);
  if (connect[ref] == PASSED || (game_map.map[ref] & MASK_OCCUPIED))
    connect[ref] = CONNECTED;
  else
    connect[ref] = NOT_ACCOUNTED;
  return 1;
}

/* Mark all this player's cells, which are connected to the capital
   through own cells and other players' unoccupied cells.  */
static enum fill_op
blocked (loc)
     Coord loc;
{
  get_map_real_coord (&loc);
  if (loc.x != -1)
    {
      register int ref = parse_coord (loc);
      /* Test if this cell is yet unconnected.  */
      if (connect[ref] == UNCONNECTED
	  /* Not an other player's occupied cell.  */
	  && !((game_map.map[ref] & MASK_PLAYER) != player
	       && (game_map.map[ref] & MASK_OCCUPIED)))
	return SUCCESSFUL;
    }
  return UNSUCCESSFUL;
}

/* In first pass, mark all connected cells 'PASSED'.  */
static enum fill_op
mark_passed (loc)
     Coord loc;
{
  connect[parse_coord (loc)] = PASSED;
  return 1;
}

/* Test if this cell is unconnected.  */
static enum fill_op
not_connected (loc)
     Coord loc;
{
  get_map_real_coord (&loc);
  if (loc.x != -1)
    {
      register int ref;
      ref = parse_coord (loc);
      if (connect[ref] == UNCONNECTED)
	return SUCCESSFUL;
    }
  return UNSUCCESSFUL;
}

/* Test if this unconnected cell is neighbouring a connected or border
   cell.  If this cell belongs to the encircled player, resolve new
   ownership.  Returns successful if there are more cells to invade.
*/
static enum fill_op
mark_border (loc)
     Coord loc;
{
  register int i;
  int loc_ref;
 
  loc_ref = parse_real_coord (loc);
  for (i = 0; i < 8; i++)
    {
      Coord this_coord;
      this_coord = add_coord (loc, round_game[i]);
      get_map_real_coord (&this_coord);
      if (this_coord.x != -1)
	{
	  enum connect_type this_loc_connect;
	  this_loc_connect = connect[parse_coord (this_coord)];
	  /* Neighbouring connected cell.  */
	  if (this_loc_connect == CONNECTED)
	    {
	      connect[loc_ref] = BORDER;
	      if ((game_map.map[loc_ref] & MASK_PLAYER) == player
		  /* For general defeats.  */
		  || player == 0)
		invade (loc);	/* Encircled player's cell.  */
	      return UNSUCCESSFUL;
	    }
	}
    }
  connect[loc_ref] = PASSED;
  /* Return successful if there are more enemy cells to invade.  */
  if ((game_map.map[loc_ref] & MASK_PLAYER) == player || player == 0)
    return SUCCESSFUL;
  else
    return UNSUCCESSFUL;
}

static int
search_encircled ()
{
  register int i, ran_2nd_pass = 0;
  for (i = 0; i < game_map.size; i++)
    {
      if (connect[i] == UNCONNECTED
	  && ((game_map.map[i] & MASK_PLAYER) == player || player == 0))
	{
	  /* Run only if necessary, since this is a quite expensive function.
	     First pass was in function fill_check.  */
	  if (!ran_2nd_pass && player != 0)
	    {
	      register int j;
	      fill (game_map.capital[player-1], &borders, &mark_connected);
	      ran_2nd_pass = 1;
	      for (j = 0; j < game_map.size; j++)
		if (connect[j] == NOT_ACCOUNTED)
		  connect[j] = UNCONNECTED;
	    }
	  fill (parse_loc (i), &not_connected, &mark_border);
	  for (i = 0; i < game_map.size; i++)
	    if (connect[i] == PASSED)
	      connect[i] = UNCONNECTED;
	    else if (connect[i] == BORDER)
	      connect[i] = CONNECTED;
	  return 1;
	}
    }
  return 0;
}

static int
fill_check ()
{
  int ret_val = 0;
  /* The check to find which cells are connected to the capital is
     being made in two steps.  In the first pass all the cells
     connected to this player's capital are marked 'PASSED'.  In the
     second pass all 'PASSED' and other players' occupied cells are
     marked 'CONNECTED'.  */
  fill (game_map.capital[player-1], &blocked, &mark_passed);
  while (search_encircled ())
    ret_val = 1;
  return ret_val;
}

static int
deadlock (loc, pattern, n_hits, invader, classify)
     Coord loc;
     int pattern;
     int *n_hits;
     unsigned char *invader;
     void (*classify) (Coord, int, int *);
{
  signed int *array;
  register int i, deadlock_stat = 0;
  int largest_val_pl = -1;

  array = (int *) my_calloc (game_map.players, sizeof (int));
  classify (loc, pattern, array);

  for (i = 0, *n_hits = 0; i < game_map.players; i++, pattern >>= 1)
    {
      if (pattern & 1)
	{
	  (*n_hits)++;
	  if (largest_val_pl == -1 || array[i] > array[largest_val_pl])
	    {
	      largest_val_pl = i;
	      deadlock_stat = 0;
	    }
	  else if (array[i] == array[largest_val_pl] && i != player-1)
	    {
	      deadlock_stat |= 1<<i;
	      deadlock_stat |= 1<<largest_val_pl;
	    }
	}
    }
  /* Array was necessary only for comparisons.  */
  free (array);
  *invader = largest_val_pl+1;
  return deadlock_stat;
}
      
static void
count_connects (loc, pattern, array)
     Coord loc;
     int pattern;
     int *array;
{
  register int i;
  for (i = 0; i < 8; i++)
    {
      Coord this_loc;
      this_loc = add_coord(loc, round_game[i]);
      get_map_real_coord (&this_loc);
      if (this_loc.x != -1)
	{
	  int ref;
	  map_val val;
	  ref = parse_real_coord (this_loc);
	  val = game_map.map[ref] & MASK_PLAYER;
	  if (connect[ref] == CONNECTED)
	    {
	      if (game_map.map[ref] & MASK_OCCUPIED)
		array[val - 1] += 3;
	      else
		array[val - 1] += 2;
	    }
	}
    }
  if (player != 0)
    for (i = 0; i < game_map.players; i++)
      array[i] *= i == game_map.turn ? 3 : 2;
}

static void
comp_inv_dist (loc, pattern, array)
     Coord loc;
     int pattern;
     int *array;
{
  register int i;
  for (i = 0; i < game_map.players; i++, pattern >>= 1)
    {
      if (pattern & 1)
	array[i] = -dist_to_capital (i, loc);
    }
}

static void
invade (loc)
     Coord loc;
{
  int deadlock_stat, n_hits;
  unsigned char invader;

  /* Primarily give the cell to the player who has the most
     neighbouring cells.  Give a bonus to the player in turn.  */
  deadlock_stat = deadlock (loc, ~(1<<(player-1)), &n_hits, &invader,
			    count_connects);
  if (deadlock_stat)
    {
      /* Secondarily give the cell to the player, who has the nearest
	 capital.  */
      deadlock_stat = deadlock (loc, deadlock_stat, &n_hits, &invader,
				comp_inv_dist);
      if (deadlock_stat)
	{
	  register int pick, i = 1;
	  /* Tertiarily make a random pick.  */
	  for (pick = rand () % n_hits; deadlock_stat; i++,
		 deadlock_stat >>= 1)
	    {
	      if (deadlock_stat & 1 && pick-- == 0)
		invader = i;
	    }
	}
    }
  set_map (loc, (get_map_val (loc) & MASK_CROSS) | invader);
}

static void
destroy ()
{
  register int i;
  game_map.players_left--;
  /* Disable the defeated player.  */
  game_map.active_players &= ~(1<<(player - 1));
  game_map.remaining_players &= ~(1<<(player - 1));
  game_map.n_active_players--;

  /* Mark all cells of the defeated player and seas to be unconnected.  */
  for (i = 0; i < game_map.size; i++)
    {
      if (game_map.map[i] == SEA_VAL)
	connect[i] = IGNORED;
      else if ((game_map.map[i] & MASK_PLAYER) == player)
	connect[i] = UNCONNECTED;
      else
	connect[i] = CONNECTED;
    }

  while (search_encircled ());
}

static void
occupy (act, cross_n)
     Action *act;
     int *cross_n;
{
  unsigned register int i;

  for (i = 0; i < act->count; i++, cross_n++)
    {
      map_val *map_ptr;

      game_map.bit_cross &= ~(1<<*cross_n);
      game_map.n_cross--;
      map_ptr = &game_map.map[parse_real_coord
			     (game_map.cross[*cross_n])];
      if ((*map_ptr & MASK_CAPITAL) && (player = *map_ptr &
					MASK_PLAYER) != game_map.turn)
	{
	  *map_ptr = game_map.turn | MASK_OCCUPIED;
	  destroy ();
	  act->type |= EVENT_DEFEAT;
	  return;
	}
      else
	{
	  *map_ptr = game_map.turn | MASK_OCCUPIED | (*map_ptr &
						      MASK_CAPITAL);
	}
    }

  for (i = 1; i <= game_map.players; i++)
    {
      if (i == game_map.turn)
	continue;
      else
	{
	  int j;
	  for (j = 0; j < game_map.size; j++)
	    connect[j] = game_map.map[j] == SEA_VAL ? IGNORED : UNCONNECTED;
	}
      player = i;
      /* FIXME: a quick check to avoid the expensive fill_check (),
	 when it is absolutely certain it's not needed.  */
      if (fill_check ())
	act->type |= EVENT_ENCIRCLEMENT;
    }
}

extern void
action (action, act)
     int action;
     Action *act;
{
  if (action == 0)
    action = game_map.def_mode;
  /* Impossible action.  */
  if (!(game_map.modes & action))
    {
      act->type = 0;
      return;
    }
  switch (action)
    {
    case MODE_ATT:
      {
	int i;
	/* Make sure the selected mode isn't something it shouldn't be.  */
	game_map.sel_mode = MODE_ATT;
	if (++game_map.n_att == ATT_PER_TURN)
	  act->type = EVENT_ATT | EVENT_NEWTURN;
	else
	  {
	    act->type = EVENT_ATT;
	    game_map.modes = MODE_ATT;
	  }
	i = rand() % 6;
	if ((game_map.bit_cross >> i) & 1)
	  {
	    int *cross_n;
	    cross_n = &i;
	    act->loc = (Coord *) my_malloc (sizeof (Coord));
	    *act->loc = game_map.cross[i];
	    act->count = 1;
	    occupy (act, cross_n);
	  }
	else
	  {
	    act->loc = NULL;
	    act->count = 0;
	  }
      }
      break;
    case MODE_DEF:
      {
	int cross_n_array[3];
	register int i, n_def_cells;
	act->type = EVENT_DEF | EVENT_NEWTURN;
	act->loc = (Coord *) my_malloc (3*sizeof (Coord));
	for (i = 0, n_def_cells = 0; i < CROSS_MAX; i++)
	  {
	    if ((game_map.bit_cross >> i) & 1)
	      {
		act->loc[n_def_cells] = game_map.cross[i];
		cross_n_array[n_def_cells++] = i;
	      }
	  }
	act->count = n_def_cells;
	if (n_def_cells)
	  {
	    occupy (act, cross_n_array);
	    break;
	  }
	else
	  {
	    free (act->loc);
	    act->loc = NULL;
	  }
      }
      break;

    case MODE_GUE:
      act->type = EVENT_GUE | EVENT_NEWTURN;
      {
	int i;
	for (i = 0; i < CROSS_MAX; i++)
	  {
	    if ((game_map.bit_cross >> i) & 1)
	      {
		int *cross_n;
		cross_n = &i;
		act->loc = (Coord *) my_malloc (sizeof (Coord));
		*act->loc = game_map.cross[i];
		act->count = 1;
		occupy (act, cross_n);
		break;
	      }
	  }
	if (i == CROSS_MAX)
	  {
	    act->loc = NULL;
	    act->count = 0;
	  }
      }
    }

  /* Check if any of the currently inactive players should be active because
     of encirclement.  Encircled cells turn to unoccupied cells.  */
  if (act->type & EVENT_ENCIRCLEMENT
      && game_map.active_players != game_map.remaining_players)
    {
      register int i;
      for (i = 0; i < game_map.players; i++)
	{
	  register int mask = 1<<i;
	  if (!(mask & game_map.active_players)
	      && fill (game_map.capital[game_map.turn - 1], &search_contact,
		       &mark_connected) == BREAK)
	    game_map.active_players |= mask;
	}
    }

  if ((act->type & EVENT_NEWTURN) && new_turn ())
    act->type |= EVENT_UNOCCUPY;
}

static enum fill_op
search_contact (loc)
     Coord loc;
{
  int ref;
  get_map_real_coord (&loc);
  if (loc.x == -1)
    return UNSUCCESSFUL;
  ref = parse_real_coord (loc);
  if (connect[ref] != UNCONNECTED || game_map.map[ref] == SEA_VAL)
    return UNSUCCESSFUL;
  if ((game_map.map[ref] & MASK_PLAYER) == player)
    {
      if (!(game_map.map[ref] & MASK_OCCUPIED))
	{
	  register int i;
	  for (i = 0; i < 8; i++)
	    {
	      Coord neighbour;
	      neighbour = add_coord (loc, round_game[i]);
	      get_map_real_coord (&neighbour);
	      if (neighbour.x != -1)
		{
		  map_val this_val;
		  this_val = get_map_val (neighbour);
		  /* Enemy cell neighbouring an unoccupied cell.  */
		  if (this_val != SEA_VAL
		      && (this_val & MASK_PLAYER) != game_map.turn)
		    return BREAK;
		}
	    }
	}
      return SUCCESSFUL;
    }
  if (game_map.map[ref] & MASK_OCCUPIED)
    return UNSUCCESSFUL;
  /* Enemy unoccupied cell.  */
  return BREAK;
}

/* Some heavy bit flipping.  */
static void
loop_by_bit_mask (start, bit_mask)
     char *start;
     int bit_mask;
{
  register int i;
  /* Test if there are no entries left after this player.  */
  if (!(~((1<<*start) - 1) & bit_mask))
    {
      for (i = 1; !(bit_mask & 1); i++, bit_mask >>= 1);
    }
  else
    /* Otherwise search the next entry.  */
    {
      bit_mask >>= *start;
      for (i = *start + 1; !(bit_mask & 1); i++, bit_mask >>= 1);
    }
  *start = i;
}

static int
disable_player ()
{
  if (--game_map.n_active_players == 1)
    {
      /* Reset all occupied cells to unoccupied status.  Change
	 turn to the next player in turn to begin.  */
      game_map.n_active_players = game_map.players_left;
      game_map.active_players = game_map.remaining_players;
      loop_by_bit_mask (&game_map.begin_turn,
			game_map.remaining_players);
      game_map.turn = game_map.begin_turn;
      clear_map_bit ((map_val) ~(MASK_OCCUPIED | MASK_CROSS));
      return 1;
    }
  game_map.active_players &= ~(1<<(game_map.turn - 1));

  return 0;
}
  
/* Return true, if all the occupied cells are to be reverted back to
   unoccupied status.  */
static int
new_turn (void)
{
  register int i, unoccupy = 0;

  if (game_map.players_left == 1)
    return 0;

  for (i = 0; i < game_map.size; i++)
    connect[i] = UNCONNECTED;

  game_map.n_cross = game_map.bit_cross = 0;
  game_map.n_att = 0;
  game_map.modes = MODE_ATT|MODE_DEF|MODE_GUE;
  game_map.sel_mode = game_map.def_mode = MODE_DEF;

  do
    {
      loop_by_bit_mask (&game_map.turn, game_map.active_players);
      player = game_map.turn;
    }
  /* Fill returns here BREAK if there's still contact to other
     players' areas.  If not, call disable_player, and get next
     player, if not done unoccupization, in which case the next player
     in turn to begin is in turn.  */
  while (fill (game_map.capital[game_map.turn - 1], &search_contact,
	       &mark_connected) != BREAK
	 && !(unoccupy = disable_player ()));
  /* If not unoccupied, just clear crosses.  Otherwise both crosses
     and occupied cells are cleared.  */
  if (!unoccupy)
    clear_map_bit ((map_val) ~MASK_CROSS);

  return unoccupy;
}
