/*  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
*/
/* This file has everything, that happens when a cell is occupied.
   TODO: this file is quite large.  Should it be split into two files?
*/
enum connect_type {
  UNCONNECTED,
  CONNECTED,
  BORDER,
  PASSED
};

#include <limits.h>
#include <stdlib.h>

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

static enum fill_op blocked (Coord);
static enum fill_op mark_connected (Coord);
static enum fill_op not_connected (Coord);
static enum fill_op mark_border (Coord);
static int search_encircled (void);
static int fill_check (void);
static int deadlock (Coord, int, int *, 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 destroy (void);
static void occupy (Action *, int *);
static enum fill_op search_contact (Coord);
static void clear_map_bit (map_val);
static void loop_by_bit_mask (char *, int);
static int get_next_player (void);
static int new_turn (void);

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

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

static enum fill_op
mark_connected (loc)
     Coord loc;
{
  int ref;
  ref = parse_real_coord (loc);
  connect[ref] = CONNECTED;
  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 (game_map.map[ref] != SEA_VAL && 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 this_loc_ref;
 
  this_loc_ref = parse_real_coord (loc);
  connect[this_loc_ref] = PASSED;
  for (i = 0; i < 8; i++)
    {
      Coord this_coord;
      this_coord = add_coord (loc, round[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)];
	  if (this_loc_connect == CONNECTED
	      || this_loc_connect == BORDER)
	    {
	      connect[this_loc_ref] = BORDER;
	      if ((game_map.map[this_loc_ref] & MASK_PLAYER) == player)
		invade (loc);
	      return UNSUCCESSFUL;
	    }
	}
    }
  /* Return successful if there are more enemy cells to invade.  */
  if ((game_map.map[this_loc_ref] & MASK_PLAYER) == player)
    return SUCCESSFUL;
  else
    return UNSUCCESSFUL;
}

static int
search_encircled ()
{
  register int i;
  for (i = 0; i < game_map.size; i++)
    {
      if (connect[i] == UNCONNECTED
	  && (game_map.map[i] & MASK_PLAYER) == player)
	{
	  fill (parse_loc (i), &not_connected, &mark_border);
	  return 1;
	}
    }
  return 0;
}

static int
fill_check ()
{
  int ret_val = 0;
  fill (game_map.capital[player-1], &blocked, &mark_connected);
  while (search_encircled ())
    {
      register int i;
      for (i = 0; i < game_map.size; i++)
	{
	  if (connect[i] == PASSED)
	    connect[i] = UNCONNECTED;
	}
      ret_val = 1;
    }
  return ret_val;
}

static int
deadlock (loc, pattern, n_hits, classify)
     Coord loc;
     int pattern;
     int *n_hits;
     void (*classify) (Coord, int, int *);
{
  signed int *array;
  register int i, deadlock_stat = 0;
  /* The only player which is ignored in these calculations and can be
     used as a 'null' player.  */
  int largest_val_pl = player-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 (array[i] > array[largest_val_pl])
	    {
	      largest_val_pl = i;
	      deadlock_stat = 0;
	    }
	  else if (array[i] == array[largest_val_pl])
	    {
	      deadlock_stat |= 1<<i;
	      deadlock_stat |= 1<<largest_val_pl;
	    }
	}
    }
  /* Array was necessary only for comparisons.  */
  free (array);
  return deadlock_stat ? deadlock_stat : -(largest_val_pl+1);
}
      
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[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] == BORDER && val != SEA_VAL)
	    array[val - 1]++;
	}
    }
}

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 (player, loc);
      else
	array[i] = INT_MIN;
    }
}

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

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

static void
destroy ()
{
  register int i;
  /* TODO: please do a little more sophisticated end of game.  */
  if (--game_map.players_left == 1)
    exit (0);
  /* 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 to be unconnected.  */
  for (i = 0; i < game_map.size; i++)
    connect[i] = (game_map.map[i] & MASK_PLAYER) == player;
  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)
	  && (*map_ptr & MASK_PLAYER) != game_map.turn)
	{
	  *map_ptr = game_map.turn | MASK_OCCUPIED;
	  player = game_map.turn;
	  destroy ();
	}
      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;
      player = i;
      /* TODO: a quick check to avoid the expensive fill_check (),
	 when its 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;
    }
  connect = my_calloc (1, sizeof (enum connect_type) * game_map.size);
  switch (action)
    {
    case MODE_ATT:
      {
	int i;
	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;
      if (game_map.n_cross > 0)
	{
	  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;
	    }
	}
    }
  if ((act->type & EVENT_NEWTURN) && new_turn ())
    act->type |= EVENT_UNOCCUPY;
      
  free (connect);
  connect = NULL;
}

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[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;
}

static void
clear_map_bit (status)
     map_val status;
{
  register int i;
  for (i = 0; i < game_map.size; i++)
    game_map.map[i] &= status;
}

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
get_next_player ()
{
  /* Test if this player has contact to any enemy unoccupied cells, or
     if any other player has occupied cells next to this player's
     unoccupied cells.  */
  if (fill (game_map.capital[game_map.turn - 1], &search_contact,
	    &mark_connected) != BREAK)
    {
      game_map.n_active_players--;
      game_map.active_players ^= 1<<(game_map.turn - 1);
      if (game_map.n_active_players == 1)
	{
	  /* Reset all occupied cells to unoccupied status.  Change
	     turn to the next player in turn.  */
	  game_map.n_active_players = game_map.players;
	  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;
	}
    }
  clear_map_bit ((map_val) ~MASK_CROSS);

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

  player = game_map.turn;
  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.def_mode = MODE_ATT;
  return get_next_player ();
}
