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

#define rand_in_range(a,b)   ((b-a)*rand()/(RAND_MAX+1.0)+a)
#define PLASMA_NIL -100000.0

#include <config.h>

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

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

static void attr_str_parse (const char *, double *, double *, double *,
			    double *, double *);
static void place_capitals (double);
static double influence (const int, const int, const int, const int);
static void calculate_weights (double *, map_val *);
static double adjust (const int, const int, const int, const int,
		      const int, const int, const double);
static void subdiv (const double, const double, const double, const double);
static void generate_plasma (double *);
static enum fill_op unmarked_land (Coord);
static enum fill_op mark_cross (Coord);
static int accessible ();

static double *plasma;
static double plasma_min = 0.0, plasma_max = 0.3, granularity = 0.03;
static double plasma_rand_range;
static int plasma_side, plasma_size, plasma_level = 3;
static double map_dim;

static void
place_capitals (distance_min)
     double distance_min;
{
  register int i;
  int too_close = 1;
  distance_min *= distance_min;
  while (too_close)
    {
      for (i = 0; i < game_map.players; i++)
	{
	  game_map.capital[i].x = (int)
	    (((double)game_map.width)*rand()/(RAND_MAX+1.0));
	  game_map.capital[i].y = (int)
	    (((double)game_map.height)*rand()/(RAND_MAX+1.0));
	}
      too_close = 0;
      for (i = 0; i < game_map.players && !too_close; i++)
	{
	  register int j;
	  for (j = 0; j < game_map.players; j++)
	    {
	      int legx, legy;
	      if (i == j)
		continue;
	      legx = game_map.capital[i].x-game_map.capital[j].x;
	      legx *= legx;
	      legy = game_map.capital[i].y-game_map.capital[j].y;
	      legy *= legy;
	      if (legx+legy < distance_min)
		{
		  too_close = 1;
		  break;
		}
	    }
	}
    }
}

static double
influence (x1, y1, x2, y2)
     const int x1, y1, x2, y2;
{
  int legx = x1 > x2 ? (x1-x2) : (x2-x1);
  int legy = y1 > y2 ? (y1-y2) : (y2-y1);
  return map_dim / (hypot ((double) legx, (double) legy)+1);
}

static void
calculate_weights (weights, map)
     double *weights;
     map_val *map;
{
  register int i, j;
  int index = 0;
  double normalize = 0.0;
  for (i = 0; i < game_map.players; i++)
    {
      double this_value = 0.0;
      for (j = 0; j < game_map.players; j++)
	this_value += influence (game_map.capital[i].x, game_map.capital[i].y,
				 game_map.capital[j].x, game_map.capital[j].y);
      if (this_value > normalize)
	normalize = this_value;
    }

  for (i = 0; i < game_map.height; i++)
    for (j = 0; j < game_map.width; j++, index++)
      {
	register int k;
	short nearest_player = 0;
	double total_weight = 0.0, largest_value = 0.0;
	for (k = 0; k < game_map.players; k++)
	  {
	    double this_value;
	    this_value = influence (j, i, game_map.capital[k].x,
				    game_map.capital[k].y);
	    if (largest_value < this_value)
	      {
		largest_value = this_value;
		nearest_player = k+1;
	      }
	    total_weight += this_value/normalize;
	  }
	map[index] = nearest_player;
	weights[index] = total_weight;
      }

  for (i = 0; i < game_map.players; i++)
    map[parse_coord(game_map.capital[i])] |= MASK_CAPITAL;
}

static double
adjust (x1, y1, x2, y2, x3, y3, amount)
     const int x1, y1, x2, y2, x3, y3;
     const double amount;
{
  int index;

  index = (y2<<plasma_level)|x2;
  if (plasma[index] != PLASMA_NIL)
    return;

  plasma[index] =
    ((plasma[(y1<<plasma_level)|x1]+plasma[(y3<<plasma_level)|x3]) / 2.0
     + (rand_in_range (-plasma_rand_range, plasma_rand_range) * amount));
  if (plasma[index] > plasma_max)
    plasma[index] = plasma_max;
  else if (plasma[index] < plasma_min)
    plasma[index] = plasma_min;
  return plasma[index];
}

static void
subdiv (x1, y1, x2, y2)
     const double x1, y1, x2, y2;
{
  double adjust_amount, center;
  int x, y, index;

  if (x2-x1 < 2)
    return;

  x = x1+x2;
  y = y1+y2;
  x >>= 1;
  y >>= 1;

  adjust_amount = (x2-x1)*granularity;
  center =  adjust (x1, y1, x, y1, x2, y1, adjust_amount);
  center += adjust (x1, y1, x1, y, x1, y2, adjust_amount);
  center += adjust (x1, y2, x, y2, x2, y2, adjust_amount);
  center += adjust (x2, y1, x2, y, x2, y2, adjust_amount);

  index = y<<plasma_level|x;
  if (plasma[index] == PLASMA_NIL)
    plasma[index] = center/4;

  subdiv (x1, y1, x , y );
  subdiv (x , y1, x2, y );
  subdiv (x1, y , x , y2);
  subdiv (x , y , x2, y2);
}

static void
generate_plasma (weight_map)
     double *weight_map;
{
  int i, powerof2_sqr = 8, w_index = 0;

  plasma_side = game_map.width > game_map.height ? game_map.width :
    game_map.height;
  plasma_level = 3;
  while (plasma_side >= powerof2_sqr)
    {
      powerof2_sqr <<= 1;
      plasma_level++;
    }
  plasma_side = powerof2_sqr;
  plasma_size = plasma_side*plasma_side;
  plasma = my_malloc (plasma_size*sizeof(double));
  plasma_rand_range = (plasma_max-plasma_min)/2.0;

  for (i = 0; i < plasma_size; i++)
    plasma[i] = PLASMA_NIL;
  plasma[0] = rand_in_range (plasma_min, plasma_max);
  plasma[plasma_side-1] = rand_in_range (plasma_min, plasma_max);
  plasma[plasma_size-plasma_side-1] = rand_in_range (plasma_min, plasma_max);
  plasma[plasma_size-1] = rand_in_range (plasma_min, plasma_max);

  subdiv (0, 0, plasma_side-1, plasma_side-1);

  for (i = 0; i < game_map.height; i++)
    {
      int j, p_index;
      for (j = 0, p_index = plasma_side*i; j < game_map.width;
	   j++, p_index++, w_index++)
	weight_map[w_index] -= plasma[p_index];
    }
}

static enum fill_op
unmarked_land (loc)
     Coord loc;
{
  int index = parse_real_coord (loc);
  if (index != -1 && game_map.map[index] != SEA_VAL
      && !(game_map.map[index] & MASK_CROSS))
    return SUCCESSFUL;
  return UNSUCCESSFUL;
}

static enum fill_op
mark_cross (loc)
     Coord loc;
{
  game_map.map[parse_coord (loc)] |= MASK_CROSS;
  return SUCCESSFUL;
}

static int
accessible ()
{
  register int i;
  fill (game_map.capital[0], unmarked_land, mark_cross);
  for (i = 0; i < game_map.players; i++)
    if (!(game_map.map[parse_coord (game_map.capital[i])] & MASK_CROSS))
      return 0;
  return 1;
}

extern void
create_map (players, width, height, distance_min, threshold,
	    a_plasma_min, a_plasma_max, a_granularity, randomness)
     const char players;
     const short width, height;
     double distance_min;
     const double threshold, a_plasma_min, a_plasma_max;
     const double a_granularity, randomness;
{
  register int i;
  double *weight_map, *weight_map_template;
  map_val *game_map_template;

  /* Sanity check.  */
  if (width < 4)
    game_map.width = 4;
  else
    game_map.width = width;
  if (height < 4)
    game_map.height = 4;
  else
    game_map.height = height;
  if (distance_min < 1.0)
    distance_min = 1.0;
  if (a_plasma_min > a_plasma_max)
    plasma_min = a_plasma_max;
  else
    plasma_min = a_plasma_min;

  plasma_max = a_plasma_max;
  granularity = a_granularity;

  game_map.players = players;

  map_dim = (game_map.width+game_map.height)/2;
  init_map ();
  game_map.remaining_players = (1<<game_map.players)-1;
  game_map.active_players = game_map.remaining_players;
  game_map.modes = MODE_ATT|MODE_DEF|MODE_GUE;
  game_map.def_mode = game_map.sel_mode = MODE_DEF;
  game_map.warped = 0;
  game_map.turn = 1;

  weight_map = my_malloc (game_map.size * sizeof (double));
  weight_map_template = my_malloc (game_map.size * sizeof (double));
  game_map_template = my_malloc (game_map.size * sizeof (double));

  /* No checks or safeguards present to prevent this.  This'll need something
     done if motti is ever to be more interactive.  */
  printf ("motti: terraforming, this might cause an infinite loop with bad parameters\n");
  place_capitals (distance_min);
  /* Initialize the weight map with higher values near the capitals.
     Use the same information to determine terrain controllers.  This
     distribution doesn't try to give just amounts of territory, but you'll
     have to anyway try a few times to get a decent map.  This information is
     place to templates, to avoid calculating this data on every loop.  */
  calculate_weights (weight_map_template, game_map_template);

  do
    {
      /* Copy data from templates to start over.  */
      memcpy (weight_map, weight_map_template,
	      game_map.size * sizeof (double));
      memcpy (game_map.map, game_map_template,
	      game_map.size * sizeof (map_val));

      /* Calculate fractal plasma cloud and subtract it from the initial
	 weight map.  */
      generate_plasma (weight_map);

      for (i = 0; i < game_map.size; i++)
	{
	  /* Add some randomness to make things more interesting.  */
	  weight_map[i] += randomness*rand()/(RAND_MAX+1.0) - randomness/2.0;

	  /* Use the weight map information to submerge some land.  */
	  if (weight_map[i] < threshold && !(game_map.map[i] & MASK_CAPITAL))
	    game_map.map[i] = SEA_VAL;
	}

      /* Start over if all capitals are not reachable.  */
    } while (!accessible ());

  /* Remove all inaccessible areas.  */
  for (i = 0; i < game_map.size; i++)
    {
      if (game_map.map[i] & MASK_CROSS)
	game_map.map[i] &= ~MASK_CROSS;
      else
	game_map.map[i] = SEA_VAL;
    }

  free (weight_map);
  free (weight_map_template);
  free (game_map_template);
  free (plasma);
}
