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

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

#include <map.h>
#include <occupy.h>
#include <wrappers.h>

struct map_struct game_map = {0};
const Coord round_game [] = {
  {-1, -1},
  { 0, -1},
  { 1, -1},
  { 1,  0},
  { 1,  1},
  { 0,  1},
  {-1,  1},
  {-1,  0}};

static int noslashes (char *);
static char * buildpath (char *);
static int read_number (FILE *, int *);
static int get_char (FILE *);
static void free_map ();
static enum cross_act check_surrounding (Coord, map_val);
static void set_modes ();
static int min (int, int);

extern void
init_map ()
{
  game_map.begin_turn = 1;
  game_map.players_left = game_map.players;
  game_map.n_active_players = game_map.players;
  game_map.n_cross=0;
  game_map.bit_cross=0;

  game_map.size = game_map.width * game_map.height;
  game_map.map = (map_val *) my_malloc (game_map.size * sizeof (map_val));
  game_map.capital = (Coord *) my_malloc (game_map.players * sizeof (Coord));
}

static int
noslashes (str)
     char *str;
{
  while (*str != '\0')
    if (*str++ == '/')
      return 0;
  return 1;
}

static char *
buildpath (str)
     char *str;
{
  char *name = my_malloc (sizeof (MAPPATH) + strlen (str)+1);
  sprintf (name, "%s/%s", MAPPATH, str);
  return name;
}

static int
read_number (file, status)
     FILE *file;
     int *status;
{
  int c;
  char buf[80];

  if (*status != 0)
    return 0;

  while (!isdigit (c=getc (file)))
    {
      if (c == '\n')
	{
	  *status = 1;
	  return 0;
	}
    }
  ungetc (c, file);

  return atoi (fgets (buf, 79, file));
}

static int
get_char (file)
     FILE *file;
{
  int c;
  c = getc (file);
  if (c != '\n')
    ungetc (c, file);
  c = getc (file);
  return c;
}

static void
free_map ()
{
  free (game_map.map);
  free (game_map.capital);
}

extern void
load_map (map_dir)
     char *map_dir;
{
  FILE *file;
  int status = 0;
  register int i;

  if (!(file = fopen (map_dir, "r")))
    {
      /* Try to open a map file in default map directory.  */
      char *full_name = buildpath (map_dir);
      if (noslashes (map_dir) && (file = fopen (full_name, "r")))
	free (full_name);
      else
	die ("can't open file %s", map_dir);
    }

  game_map.width=read_number (file, &status);
  game_map.height=read_number (file, &status);
  game_map.warped=read_number (file, &status);
  game_map.players=read_number (file, &status);
  game_map.active_players=read_number (file, &status);
  game_map.remaining_players=read_number (file, &status);
  game_map.turn=read_number (file, &status);
  game_map.n_att=read_number (file, &status);

  if (status != 0)
    die ("invalid map file header: %s", map_dir);

  init_map ();

  for (i = 0; i < game_map.size; i++)
    {
      int c;
      c = get_char (file);
      if (c == EOF)
	{
	  free_map ();
	  die ("unexpected EOF while reading map file: %s", map_dir);
	}
      if (islower (c))
	{
	  /* Number zero is reserved for sea */
	  game_map.map[i] = c-('a'-1);
	}
      else if (isupper (c))
	{
	  game_map.map[i] = (c-('A'-1)) | MASK_OCCUPIED;
	}
      else if (c == ' ')
	{
	  game_map.map[i] = 0;
	}
      else
	{
	  free_map ();
	  die ("invalid map file: %s", map_dir);
	}

      c = get_char (file);
      if (c == '.' || c == '*')
	{
	  game_map.capital[(game_map.map[i] & MASK_PLAYER) - 1] =
	    parse_loc (i);
	  game_map.map[i] |= MASK_CAPITAL;
	}
      else if (c == 'x' || c == 'X')
	{
	  game_map.cross[game_map.n_cross++] = parse_loc (i);
	  game_map.map[i] |= MASK_CROSS;
	  game_map.bit_cross |= 1;
	  game_map.bit_cross <<= 1;
	}
      else if (c == EOF)
	{
	  free_map ();
	  die ("unexpected EOF while reading map file: %s", map_dir);
	}
    }
  fclose (file);
  game_map.bit_cross >>= 1;
  set_modes ();
  game_map.sel_mode = game_map.def_mode;
}

extern void
save_map (file_name)
     const char *file_name;
{
  register int x, y, index;
  FILE *file;
  file = fopen (file_name, "w");
  if (!file)
    {
      perror ("motti: can't open map save file");
      exit (1);
    }

  /* Uh, I'm lazy.  If the write fails here, it will fail also later...  */
  fprintf (file, "width %d\n", game_map.width);
  fprintf (file, "height %d\n", game_map.height);
  fprintf (file, "warped %d\n", game_map.warped);
  fprintf (file, "players %d\n", game_map.players);
  fprintf (file, "active_players %d\n", game_map.active_players);
  fprintf (file, "remaining_players %d\n", game_map.remaining_players);
  fprintf (file, "turn %d\n", game_map.turn);
  fprintf (file, "n_att %d\n", game_map.n_att);

  for (y = 0, index = 0; y < game_map.height; y++)
    {
      int error;
      for (x = 0; x < game_map.width; x++, index++)
	{
	  if (game_map.map[index] == SEA_VAL)
	    error = fwrite ("  ", 1, 2, file);
	  else
	    {
	      error = fputc ((game_map.map[index] & MASK_PLAYER)
			     + ((game_map.map[index] & MASK_OCCUPIED)
				? 'A'-1 : 'a'-1), file);
	      if (game_map.map[index] & MASK_CAPITAL)
		error = fputc ((game_map.map[index] & MASK_CROSS)
			       ? 'X' : '.', file);
	      else
		error = fputc ((game_map.map[index] & MASK_CROSS)
			       ? 'x' : ' ', file);
	    }
	  if (error == EOF)
	    goto fail;
	}
      error = fputc ('\n', file);
      if (error == EOF)
	{
	fail:
	  die ("map write failed");
	}
    }
  fclose (file);
}

extern void
get_map_real_coord (loc)
     Coord *loc;
{
  if (!game_map.warped)
    {
      /* Leftmost and upmost points are both zero, bottom is game_map.height
	 minus one and rightmost point is game_map.width minus one.  */
      if (loc->x < 0 || loc->x >= game_map.width
	  || loc->y < 0 || loc->y >= game_map.height)
	{
	  loc->x = -1;
	  loc->y = -1;
	}
    }
  else
    {
      while(loc->x < 0)
	loc->x += game_map.width;
      while(loc->y < 0)
	loc->y += game_map.height;
      loc->x %= game_map.width;
      loc->y %= game_map.height;
    }
}

/* This should really have a better name, this is a bit misleading.  If I were
   ever to debug wrapped maps, then this should definitely be fixed.  */
extern int
parse_real_coord (loc)
     Coord loc;
{
  get_map_real_coord (&loc);
  return loc.x+game_map.width*loc.y;
}

extern int
parse_coord (loc)
     Coord loc;
{
  return loc.x+game_map.width*loc.y;
}

extern Coord
parse_loc (ref)
     int ref;
{
  Coord loc;
  loc.x = ref % game_map.width;
  loc.y = ref / game_map.width;
  return loc;
}

extern map_val
get_map_val (loc)
     Coord loc;
{
  get_map_real_coord (&loc);

  if (loc.x == -1)
    return 0;
  return game_map.map[parse_coord (loc)];
}

extern Coord
add_coord (a, b)
     Coord a, b;
{
  Coord ret_coord;
  ret_coord.x = a.x+b.x;
  ret_coord.y = a.y+b.y;
  return ret_coord;
}

extern int
cmp_coord (a, b)
     Coord a, b;
{
  get_map_real_coord (&a);
  get_map_real_coord (&b);

  if (a.x == b.x && a.y == b.y)
    return 1;
  else
    return 0;
}

extern void
set_map (pos, status)
     Coord pos;
     map_val status;
{
  get_map_real_coord (&pos);
  game_map.map[parse_coord (pos)] = status;
}

extern void
set_map_bit (pos, status)
     Coord pos;
     map_val status;
{
  get_map_real_coord (&pos);
  game_map.map[parse_coord (pos)] |= status;
}

extern 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 enum cross_act
check_surrounding (loc, me)
     Coord loc;
     map_val me;
{
  unsigned register char i, guerilla_possible_flag = 0;

  for (i = 0; i < 8; i++)
    {
      if ((get_map_val (add_coord (loc, round_game[i])) & MASK_PLAIN) == me)
	{
	  /* Return ANY_CROSS, if all modes possible, else
	     ONLY_GUERILLA, for guerilla.  */
	  if(i & 1)
	    return ANY_CROSS;
	  else
	    guerilla_possible_flag = 1;
	}
    }
  if (guerilla_possible_flag)
    return ONLY_GUERILLA;
  else
    return NO_CROSS;
}

extern enum cross_act
check_cross (loc)
     Coord loc;
{
  register int i;
  map_val val;

  val = get_map_val (loc);
  /* Sea can't be conquered.  */
  if (val == SEA_VAL)
    return NO_CROSS;
  /* Return true if a cross is already here.  An already existing
     cross can always be removed.  */
  if (val & MASK_CROSS)
    return IS_CROSS;
  /* Can't add a cross if there's already the maximum number of
     crosses or if the cell is already occupied.  */
  if (game_map.n_cross < CROSS_MAX && !(val & MASK_OCCUPIED))
    {
      /* A cross can be added on own area.  */
      if (game_map.turn == (val & MASK_PLAYER)
	  && (game_map.modes & (MODE_ATT|MODE_DEF)))
	return ANY_CROSS;

      if (game_map.n_cross == 0 && game_map.n_att == 0)
	{
	  /* Returns value ONLY_GUERILLA if only guerilla action is
	     possible.  */
	  return check_surrounding (loc, game_map.turn | MASK_OCCUPIED);
	}
      /* Attack or defend action is possible.  */
      else if (game_map.modes & (MODE_ATT|MODE_DEF))
	{
	  /* Skip diagonally adjacent cells.  */
	  for (i = 1; i < 8; i+=2)
	    {
	      if ((get_map_val (add_coord (loc, round_game[i])) &
		   MASK_PLAIN) == (game_map.turn | MASK_OCCUPIED))
		return ANY_CROSS;
	    }
	}
    }
  /* This function returns NO_CROSS when trying to add more crosses in
     these situations:
     1. This is a sea cell.
     2. There is already the maximum number (default 6) of crosses.
     3. This cell is already occupied.
     4. There is already one cross in guerilla mode.
     5. This cell is neither neighbouring this player's occupied cell
     or on the player's own territory.
     Removing a cross is always possible.  */
  return NO_CROSS;
}

extern int
toggle_cross_nocheck (loc, check_result)
     const Coord loc;
     const enum cross_act check_result;
{
  if (check_result == IS_CROSS)
    {
      unsigned register char i;
      for (i = 0; i < CROSS_MAX; i++)
	{
	  unsigned register char bit_mask;
	  bit_mask = 1<<i;
	  if (game_map.bit_cross & bit_mask)
	    {
	      if (cmp_coord (game_map.cross[i], loc))
		{
		  game_map.bit_cross ^= 1<<i;
		  game_map.n_cross--;
		  set_map (loc, get_map_val (loc) ^ MASK_CROSS);
		  set_modes ();
		  return 1;
		}
	    }
	}
    }
  else if (check_result != NO_CROSS)
    {
      unsigned register char free_cross = 0;
      while (game_map.bit_cross & 1<<free_cross)
	free_cross++;

      game_map.bit_cross |= 1<<free_cross;
      game_map.n_cross++;
      game_map.cross[free_cross] = loc;
      set_map (loc, get_map_val (loc) | MASK_CROSS);
      if (check_result == ANY_CROSS)
	set_modes ();
      else
	game_map.modes = game_map.def_mode = game_map.sel_mode = MODE_GUE;
      return 1;
    }
  return 0;
}

extern int
toggle_cross (loc)
     const Coord loc;
{
  enum cross_act cross_type;

  cross_type = check_cross (loc);
  return toggle_cross_nocheck (loc, cross_type);
}

static void
set_modes ()
{
  if (game_map.n_att >= 1)
    {
      game_map.modes = MODE_ATT;
      game_map.def_mode = MODE_ATT;
    }
  else
    {
      if (game_map.n_cross == 0)
	{
	  game_map.modes = MODE_ATT | MODE_DEF | MODE_GUE;
	  game_map.def_mode = MODE_DEF;
	}
      else if (game_map.n_cross > 3)
	{
	  game_map.modes = MODE_ATT;
	  game_map.def_mode = MODE_ATT;
	}
      else if (game_map.n_cross > 1)
	{
	  unsigned register char bit_mask;
	  Coord *crossptr = game_map.cross;
	  for (bit_mask = game_map.bit_cross; bit_mask;
	       bit_mask >>= 1, crossptr++)
	    {
	      if (bit_mask & 1)
		{
		  if ((get_map_val (*crossptr) & MASK_PLAYER)
		      != game_map.turn)
		    {
		      game_map.modes = MODE_ATT;
		      game_map.def_mode = MODE_ATT;
		      game_map.sel_mode = game_map.def_mode;
		      return;
		    }
		}
	    }
	  game_map.modes = MODE_ATT | MODE_DEF;
	  game_map.def_mode = MODE_DEF;
	}
      else
	{
	  unsigned register char bit_mask;
	  Coord *crossptr = game_map.cross;
	  for (bit_mask = game_map.bit_cross; bit_mask;
	       bit_mask >>= 1, crossptr++)
	    {
	      if (bit_mask & 1)
		{
		  if ((get_map_val (*crossptr) & MASK_PLAYER)
		      == game_map.turn)
		    {
		      game_map.modes = MODE_ATT | MODE_DEF | MODE_GUE;
		      game_map.def_mode = MODE_ATT;
		    }
		  else
		    {
		      game_map.modes = MODE_ATT | MODE_GUE;
		      game_map.def_mode = MODE_ATT;
		    }
		  /* Stop search.  */
		  break;
		}
	    }
	}
    }
  /* If selected mode becomes impossible, revert to default action.  */
  if (!(game_map.sel_mode & game_map.modes))
    game_map.sel_mode = game_map.def_mode;
}

static int
min (a1, a2)
     int a1, a2;
{
  return a1 < a2 ? a1 : a2;
}

extern int
dist_to_capital (player, loc)
     int player;
     Coord loc;
{
  int x_dist, y_dist;
  if (game_map.warped)
    {
      x_dist = min (abs (game_map.capital[player].x - loc.x),
		    game_map.capital[player].x > loc.x ?
		    abs ((game_map.capital[player].x -
			  game_map.width) - loc.x) :
		    abs ((loc.x - game_map.width) -
			 game_map.capital[player].x));
      y_dist = min (abs (game_map.capital[player].y - loc.y),
		    game_map.capital[player].y > loc.y ?
		    abs ((game_map.capital[player].y -
			  game_map.height) - loc.y) :
		    abs ((loc.y - game_map.height) -
			 game_map.capital[player].y));
    }
  else
    {
      x_dist = abs (game_map.capital[player].x - loc.x);
      y_dist = abs (game_map.capital[player].y - loc.y);
    }
  /* No need to take root, since the value is only used in
     comparisons.  */
  return x_dist*x_dist + y_dist*y_dist;
}

extern char
need_update (last_modes)
     char *last_modes;
{
  /* There's no harm in these two being static, even when using threads.  This
     function is called by only one thread at a time, and after change of a
     controlling thread these two get reinitalized.  */
  static last_attacks, last_turn;
  register char update = 0;

  /* A situation where no modes are available is impossible, so it's used to
     signal a full refresh.  */
  if (*last_modes == 0)
    {
      *last_modes = ~game_map.modes;
      last_attacks = game_map.n_att;
      last_turn = game_map.turn - 1;
    }

  if (game_map.modes & MODE_ATT)
    {
      if (!(*last_modes & MODE_ATT))
	{
	  update = MODE_ATT;
	  last_attacks = game_map.n_att;
	}
      else if (game_map.n_att != last_attacks)
	{
	  update = MODE_ATT;
	  last_attacks = game_map.n_att;
	}
    }
  else if ((*last_modes & MODE_ATT) && !(game_map.modes & MODE_ATT))
    update = MODE_ATT;

  /* xor */
  if (!(game_map.modes & MODE_DEF) != !(*last_modes & MODE_DEF))
    update |= MODE_DEF;
  if (!(game_map.modes & MODE_GUE) != !(*last_modes & MODE_GUE))
    update |= MODE_GUE;

  if (game_map.turn != last_turn)
    {
      update |= UPDATE_TURN;
      last_turn = game_map.turn;
    }

  *last_modes = game_map.modes;
  return update;
}

/* I choose to not implement a 'select_prev' function.  */
extern void
select_next ()
{
  switch (game_map.sel_mode)
    {
    case MODE_ATT:
      if (game_map.modes & ~MODE_ATT)
	{
	  if (game_map.modes & MODE_DEF)
	    game_map.sel_mode = MODE_DEF;
	  else
	    game_map.sel_mode = MODE_GUE;
	}
      break;
    case MODE_DEF:
      if (game_map.modes & ~MODE_DEF)
	{
	  if (game_map.modes & MODE_GUE)
	    game_map.sel_mode = MODE_GUE;
	  else
	    game_map.sel_mode = MODE_ATT;
	}
      break;
      /* If no attack available, then it's quaranteed there's no
	 defend.  */
    case MODE_GUE:
      if (game_map.modes & MODE_ATT)
	game_map.sel_mode = MODE_ATT;
      break;
    }
}

extern void
clear_crosses ()
{
  game_map.n_cross = 0;
  game_map.bit_cross = 0;
  clear_map_bit ((map_val) ~MASK_CROSS);
  set_modes ();
}
