/*  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
*/
#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 [] = {
  {-1, -1},
  { 0, -1},
  { 1, -1},
  { 1,  0},
  { 1,  1},
  { 0,  1},
  {-1,  1},
  {-1,  0}};

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);

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

/* TODO: Change map file format to raw map struct when map editor is
   finished.  It'll make save game function possible, too.  Nobody
   wants to live with this mess anyway.  */
extern char *
load_map (map_dir)
     char *map_dir;
{
  FILE *file;
  int status = 0;
  register int i;

  if (!(file = fopen (map_dir, "r")))
    return alloc_err_str (1, 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)
    return alloc_err_str (2, map_dir);

  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));

  for (i = 0; i < game_map.size; i++)
    {
      int c;
      c = get_char (file);
      if (c == EOF)
	{
	  free_map ();
	  return alloc_err_str (2, 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 ();
	  return alloc_err_str (ERR_INVNAME, 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 ();
	  return alloc_err_str (2, map_dir);
	}
    }
  fclose (file);
  game_map.bit_cross >>= 1;
  set_modes ();
  return (char *) NULL;
}

extern void
get_map_real_coord (loc)
     Coord *loc;
{
  if (!game_map.warped)
    {
      /* Leftmost and upmost point 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->x >= 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;
    }
}

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

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[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[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 (Coord loc)
{
  register enum cross_act cross_type;

  cross_type = check_cross (loc);
  if (cross_type == 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 (cross_type != 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 (cross_type == ANY_CROSS)
	set_modes ();
      else
	game_map.modes = game_map.def_mode = MODE_GUE;
      return 1;
    }
  return 0;
}

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_ATT;
	}
      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;
		      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.  */
		  return;
		}
	    }
	}
    }
}

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