/* This file is part of MINPANTU.
   See file `COPYRIGHT' for pertinent copyright notices.

   The TclTk-user interface. */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <unistd.h>
#include <tcl.h>
#include <tk.h>

#include "defs.h"
#include "motion.h"
#include "computer_player.h"


/* All information is passed between Tcl and C as strings, which Tcl
   hilariously copies around (YUK!).  We need only one string where
   our new Tcl-primitives write their results. */
static char buffer[1024];


/* The following routine is used to conveniently transfer compile-time
   constants from the `defs.h'-file to the tcl-code so that they need
   not be redefined (possibly unconformantly) in the tcl-code. */

int get_const(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  static struct {
    const char *name;
    int value;
  } constant[] = {
  { "XMAX", XMAX },
  { "YMAX", YMAX },
  { "MAX_X_SPEED", MAX_X_SPEED },
  { "MAX_Y_SPEED", MAX_Y_SPEED },
  { "PLAYER0_BASE_X", PLAYER0_BASE_X },
  { "PLAYER0_BASE_Y", PLAYER0_BASE_Y },
  { "PLAYER0_BASE_RADIUS", PLAYER0_BASE_RADIUS },
  { "PLAYER1_BASE_X", PLAYER1_BASE_X },
  { "PLAYER1_BASE_Y", PLAYER1_BASE_Y },
  { "PLAYER1_BASE_RADIUS", PLAYER1_BASE_RADIUS },
  { "NUMBER_OF_BALLS", NUMBER_OF_BALLS },
  { "STEPS_PER_PLY", STEPS_PER_PLY },
  { NULL, 0 }
  };
  int i;

  if (argc != 2) {
    ip->result =
      "get_constant: expected one argument, the name of the constant";
    return TCL_ERROR;
  }

  for (i = 0; constant[i].name != NULL; i++)
    if (strcmp(constant[i].name, argv[1]) == 0) {
      sprintf(buffer, "%d", constant[i].value);
      ip->result = buffer;
      return TCL_OK;
    }
  
  sprintf(buffer, "get_constant: unknown constant `%s'", argv[1]);
  ip->result = buffer;
  return TCL_ERROR;
}



/* Game history. 

   Theoretically it would be possible to replay a whole game by only
   storing the initial states of the balls and the moves chosen by the
   players.  But in reality we are dealing with inaccurate floating
   point numbers, so instead we save the whole state after each move
   made by either player.

   Each history entry is tagged with ample information; the score,
   turn etc.

   History is maintained in two linked lists, one for the already
   played states and another acting as a redo-log. */

typedef enum { RED, BLUE, INITIAL_STATE } turn_t;

static turn_t next_turn[] = { BLUE, RED, RED };

typedef struct history_t {
  struct history_t *next;
  double state[BALL_STATE_VECTOR_LENGTH];
  int red_goals, blue_goals;
  turn_t turn;
} history_t;


static history_t *played = NULL, *unplayed = NULL;


static void history_clear_future(void)
{
  history_t *h;
  while (unplayed != NULL) {
    h = unplayed->next;
    free(unplayed);
    unplayed = h;
  }
}


static void history_clear_past(void)
{
  history_t *h;
  while (played != NULL) {
    h = played->next;
    free(played);
    played = h;
  }
}


int history_new(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  int i;

  history_clear_future();
  history_clear_past();
  assert(played == NULL && unplayed == NULL);

  played = malloc(sizeof(history_t));
  if (played == NULL) {
    fprintf(stderr, "Out of memory in `new_game'\n");
    exit(1);
  }
  
  BALL_X_LOCATION(played->state, 0) = PLAYER0_BASE_X;
  BALL_Y_LOCATION(played->state, 0) =
    PLAYER0_BASE_Y - PLAYER0_BASE_RADIUS - 20;
  BALL_X_SPEED(played->state, 0) = 0;
  BALL_Y_SPEED(played->state, 0) = 0;
  
  BALL_X_LOCATION(played->state, 1) = PLAYER1_BASE_X;
  BALL_Y_LOCATION(played->state, 1) = 
    PLAYER1_BASE_Y + PLAYER1_BASE_RADIUS + 20;
  BALL_X_SPEED(played->state, 1) = 0;
  BALL_Y_SPEED(played->state, 1) = 0;

  for (i = 0; i < NUMBER_OF_BALLS; i++)
    if (IS_GOAL_BALL(i)) {
      /* Put the goal balls to random locations in the neutral area
	 and give them a small random initial speed. */
      BALL_X_LOCATION(played->state, i) = 
	NEUTRAL_AREA_XMIN + 
	  (rand() % (NEUTRAL_AREA_XMAX - NEUTRAL_AREA_XMIN));
      BALL_Y_LOCATION(played->state, i) = 
	NEUTRAL_AREA_YMIN + 
	  (rand() % (NEUTRAL_AREA_YMAX - NEUTRAL_AREA_YMIN));
      BALL_X_SPEED(played->state, i) = -100 + (rand() % 200);
      BALL_Y_SPEED(played->state, i) = -100 + (rand() % 200);
    }

  played->red_goals = played->blue_goals = 0;
  played->turn = INITIAL_STATE;
  played->next = NULL;

  return TCL_OK;
}

/* There are two "modes" in viewing the game: the stepwise more when
   actually playing the game and taking time to think about one's
   moves, and the real-time smooth-moving instant-replay mode.

   In the stepwise mode the GUI shows the recent trajectories of the
   balls using a sequence of small dots.  Moving to a "next state in
   history" in the stepwise mode involves computing the intermediate
   states into a buffer. */

static double intermediate_state[STEPS_PER_PLY][BALL_STATE_VECTOR_LENGTH];


int history_next(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  double ball_state[BALL_STATE_VECTOR_LENGTH];
  int ball_is_in_base[NUMBER_OF_BALLS];
  int step, i, red_goals_delta = 0, blue_goals_delta = 0;
  history_t *h;

  assert(played != NULL);
    
  memcpy(ball_state, played->state, sizeof(ball_state));

  /* Initialize `ball_is_in_base' to correspond to the situation
     currently shown. */
  for (i = 0; i < NUMBER_OF_BALLS; i++)
    ball_is_in_base[i] =
      DISTANCE(BALL_X_LOCATION(ball_state, i),
	       BALL_Y_LOCATION(ball_state, i),
	       PLAYER0_BASE_X,
	       PLAYER0_BASE_Y) < PLAYER0_BASE_RADIUS
      || DISTANCE(BALL_X_LOCATION(ball_state, i),
		  BALL_Y_LOCATION(ball_state, i),
		  PLAYER1_BASE_X,
		  PLAYER1_BASE_Y) < PLAYER1_BASE_RADIUS;

  for (step = 0; step < STEPS_PER_PLY; step++) {

    integrate_step(ball_state, derive_state, INTEGRATE_STEP);

    /* Update `ball_is_in_base' and reward for goal balls entering the
       bases. */
    for (i = 0; i < NUMBER_OF_BALLS; i++)
      if (IS_GOAL_BALL(i))
	if (DISTANCE(BALL_X_LOCATION(ball_state, i), 
		     BALL_Y_LOCATION(ball_state, i),
		     PLAYER0_BASE_X,
		     PLAYER0_BASE_Y)
	    < PLAYER0_BASE_RADIUS) {
	  if (!ball_is_in_base[i]) {
	    ball_is_in_base[i] = 1;
	    blue_goals_delta++;
	  }
	} else if (DISTANCE(BALL_X_LOCATION(ball_state, i), 
			    BALL_Y_LOCATION(ball_state, i),
			    PLAYER1_BASE_X,
			    PLAYER1_BASE_Y)
		   < PLAYER1_BASE_RADIUS) {
	  if (!ball_is_in_base[i]) {
	    ball_is_in_base[i] = 1;
	    red_goals_delta++;
	  }
	} else 
	  ball_is_in_base[i] = 0;

    if (step < STEPS_PER_PLY)
      memcpy(intermediate_state[step], ball_state, sizeof(ball_state));
  }

  if (unplayed != NULL) {
    /* We were stepwisely replaying existing history -- move the
       reached history entry from the `unplayed'-list to the
       `played'-list. */
    h = unplayed->next;
    unplayed->next = played;
    played = unplayed;
    unplayed = h;
  } else {
    /* We're creating new history. */
    h = malloc(sizeof(history_t));
    if (h == NULL) {
      fprintf(stderr, "Out of memory in `history_next'\n");
      exit(1);
    }
    memcpy(h->state, ball_state, sizeof(ball_state));
    h->red_goals = played->red_goals + red_goals_delta;
    h->blue_goals = played->blue_goals + blue_goals_delta;
    h->turn = next_turn[played->turn];
    h->next = played;
    played = h;
  }

  return TCL_OK;
}


int history_rewind(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  history_t *h;

  assert(played != NULL);

  while (played->turn != INITIAL_STATE) {
    h = played->next;
    played->next = unplayed;
    unplayed = played;
    played = h;
  }
  history_next(/* Dummy arguments: */ cd, ip, argc, argv);

  return TCL_OK;
}


int history_previous(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  history_t *h;

  assert(played != NULL);

  /* Make *two* steps backward, then one `history_next' to update
     `intermediate_state's */
  if (played->turn != INITIAL_STATE) {
    h = played->next;
    played->next = unplayed;
    unplayed = played;
    played = h;
  }
  if (played->turn != INITIAL_STATE) {
    h = played->next;
    played->next = unplayed;
    unplayed = played;
    played = h;
  }
  history_next(/* Dummy arguments: */ cd, ip, argc, argv);

  return TCL_OK;
}


int history_end(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  history_t *h;

  assert(played != NULL);

  if (unplayed == NULL)
    /* We are at end; just return. */
    return TCL_OK;

  /* Move to the next to last history entry, then make one call to
     `history_next' to move to the end of history and update
     `intermediate_state's accordingly. */
  while (unplayed->next != NULL) {
    h = unplayed;
    unplayed = unplayed->next;
    h->next = played;
    played = h;
  }
  history_next(/* Dummy arguments: */ cd, ip, argc, argv);
  
  return TCL_OK;
}


int history_load(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  FILE *fp;
  int i;
  
  if (argc != 2) {
    ip->result = "history_load: expected a file name as argument.";
    return TCL_ERROR;
  }
  fp = fopen(argv[1], "r");
  if (fp == NULL) {
    /* Return a non-true value to TCL so that it may open an error
       box, if it wants. */
    ip->result = "0";
    return TCL_OK;
  }

  history_clear_future();
  history_clear_past();
  assert(played == NULL && unplayed == NULL);
  
  while (!feof(fp)) {
    history_t *h = malloc(sizeof(history_t));
    if (h == NULL) {
      fprintf(stderr, "Out of memory in `history_load %s'\n", argv[1]);
      exit(1);
    }
    for (i = 0; i < BALL_STATE_VECTOR_LENGTH; i++)
      fscanf(fp, "%lf", &h->state[i]);
    fscanf(fp, "%d %d %d\n", &h->red_goals, &h->blue_goals, &h->turn);
    h->next = played;
    played = h;
  }
  ip->result = "1";
  return TCL_OK;
}


static void history_save_past(FILE *fp, history_t *h)
{
  int i;

  if (h == NULL)
    return;

  history_save_past(fp, h->next);

  for (i = 0; i < BALL_STATE_VECTOR_LENGTH; i++)
    fprintf(fp, " %g", h->state[i]);
  fprintf(fp, " %d %d %d\n", h->red_goals, h->blue_goals, h->turn);
}

int history_save(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  FILE *fp;
  int i;
  history_t *h;
  
  if (argc != 2) {
    ip->result = "history_save: expected a file name as argument.";
    return TCL_ERROR;
  }
  fp = fopen(argv[1], "w");
  if (fp == NULL) {
    ip->result = "0";
    return TCL_OK;
  }

  history_save_past(fp, played);

  for (h = unplayed; h != NULL; h = h->next) {
    for (i = 0; i < BALL_STATE_VECTOR_LENGTH; i++)
      fprintf(fp, " %g", h->state[i]);
    fprintf(fp, " %d %d %d\n", h->red_goals, h->blue_goals, h->turn);
  }
 
  ip->result = "1";
  return TCL_OK;
}


int history_is_at_beginning(ClientData cd, Tcl_Interp *ip, 
			    int argc, char *argv[])
{
  if (played == NULL
      || played->turn == INITIAL_STATE
      || played->next->turn == INITIAL_STATE)
    ip->result = "1";
  else 
    ip->result = "0";
  return TCL_OK;
}


int history_is_at_end(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  ip->result = (unplayed == NULL) ? "1" : "0";
  return TCL_OK;
}


int history_turn(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  if (played == NULL || played->turn == INITIAL_STATE)
    ip->result = "INITIAL_STATE";
  else if (played->turn == RED)
    ip->result = "RED";
  else
    ip->result = "BLUE";
  return TCL_OK;
}


int set_user_move(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  int rx, ry, r;

  if (argc != 4) {
    ip->result = 
      "Expected three integer arguments: relative x, y, and maximum";
    return TCL_ERROR;
  }
  rx = atoi(argv[1]);
  ry = atoi(argv[2]);
  r = atoi(argv[3]);
  history_clear_future();
  BALL_X_SPEED(played->state, played->turn) = MAX_X_SPEED * rx / (double) r;
  BALL_Y_SPEED(played->state, played->turn) = MAX_Y_SPEED * ry / (double) r;
  return TCL_OK;
}


int computer_move(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  int ball_is_in_base[NUMBER_OF_BALLS];
  int i, effort, player;

  if (argc != 3) {
    ip->result = "Expected two integer arguments: red and blue effort.";
    return TCL_ERROR;
  }

  player = played->turn;
  effort = atoi(argv[player == RED ? 1 : 2]);
  history_clear_future();
  for (i = 0; i < NUMBER_OF_BALLS; i++)
    ball_is_in_base[i] =
      DISTANCE(BALL_X_LOCATION(played->state, i),
	       BALL_Y_LOCATION(played->state, i),
	       PLAYER0_BASE_X,
	       PLAYER0_BASE_Y) < PLAYER0_BASE_RADIUS
      || DISTANCE(BALL_X_LOCATION(played->state, i),
		  BALL_Y_LOCATION(played->state, i),
		  PLAYER1_BASE_X,
		  PLAYER1_BASE_Y) < PLAYER1_BASE_RADIUS;
  choose_move(effort, player, played->state, ball_is_in_base, 0, 0);

  return TCL_OK;
}


/* The two routines below are rather similar.  It might be worthwhile
   to coalescue them. */

int ball_location(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  enum { X, Y } coord;
  int ball_id;
  double *state;

  if (argc != 4) {
    ip->result = "ball: Expected three args: coordinate, ball, and state";
    return TCL_ERROR;
  }

  if (strcmp(argv[1], "x") == 0)
    coord = X;
  else if (strcmp(argv[1], "y") == 0)
    coord = Y;
  else {
    ip->result = "ball: First argument should be either x or y";
    return TCL_ERROR;
  }

  ball_id = atoi(argv[2]);
  if (ball_id < 0 || ball_id >= NUMBER_OF_BALLS) {
    ip->result = "ball: incorrect ball number";
    return TCL_ERROR;
  }

  if (strcmp(argv[3], "-current") == 0)
    state = played->state;
  else {
    int tmp;
    tmp = atoi(argv[3]);
    if (tmp < 0 || tmp >= STEPS_PER_PLY) {
      sprintf(buffer, "ball: Third argument %s not within the range [0,%d[",
	      argv[3], STEPS_PER_PLY);
      ip->result = buffer;
      return TCL_ERROR;
    }
    state = intermediate_state[tmp];
  }

  sprintf(buffer, "%d", 
	  (coord == X)
	  ? (int) BALL_X_LOCATION(state, ball_id)
	  : (int) BALL_Y_LOCATION(state, ball_id));
  ip->result = buffer;
  return TCL_OK;
}


int ball_score(ClientData cd, Tcl_Interp *ip, int argc, char *argv[])
{
  if (played == NULL)
    ip->result = " score = 0 - 0 ";
  else {
    sprintf(buffer, " score = %d - %d ", 
	    played->red_goals,  played->blue_goals);
    ip->result = buffer;
  }
  return TCL_OK;
}


/* Initializations within Tcl. */

int Tcl_AppInit(Tcl_Interp *interp)
{
  /* Init the random number generator. */
  srand(getpid());

  Tk_MainWindow(interp);
  if (Tcl_Init(interp) == TCL_ERROR)
    return TCL_ERROR;
  if (Tk_Init(interp) == TCL_ERROR)
    return TCL_ERROR;

  /* Make the functions defined in this file callable from Tcl. */
  Tcl_CreateCommand(interp, "get_const", get_const, NULL, NULL);
  Tcl_CreateCommand(interp, "history_new", history_new, NULL, NULL);
  Tcl_CreateCommand(interp, "history_rewind", history_rewind, NULL, NULL);
  Tcl_CreateCommand(interp, "history_previous", history_previous, NULL, NULL);
  Tcl_CreateCommand(interp, "history_next", history_next, NULL, NULL);
  Tcl_CreateCommand(interp, "history_end", history_end, NULL, NULL);
  Tcl_CreateCommand(interp, "history_save", history_save, NULL, NULL);
  Tcl_CreateCommand(interp, "history_load", history_load, NULL, NULL);
  Tcl_CreateCommand(interp, "history_is_at_beginning", history_is_at_beginning,
		    NULL, NULL);
  Tcl_CreateCommand(interp, "history_is_at_end", history_is_at_end, 
		    NULL, NULL);
  Tcl_CreateCommand(interp, "history_turn", history_turn, NULL, NULL);
  Tcl_CreateCommand(interp, "computer_move", computer_move, NULL, NULL);
  Tcl_CreateCommand(interp, "set_user_move", set_user_move, NULL, NULL);
  Tcl_CreateCommand(interp, "ball", ball_location, NULL, NULL);
  Tcl_CreateCommand(interp, "ball_score", ball_score, NULL, NULL);

  Tcl_SetVar(interp, "minpantu_dir", INSTALL_DIR, TCL_GLOBAL_ONLY);

  return TCL_OK;
}

int main(int argc, char **argv)
{
  /* Prepent the tcl-source file name to the command line so that we
     can execute the script *without* being interactive. */
  int i;
  char **new_argv;

  new_argv = malloc((argc + 1) * sizeof(char *));
  if (new_argv == NULL) {
    fprintf(stderr, "Out of memory\n");
    exit(1);
  }
  new_argv[0] = argv[0];
  new_argv[1] = INSTALL_DIR "/minpantu.tcl";
  for (i = 1; i <= argc; i++)
    new_argv[i+1] = argv[i];

  Tk_Main(argc + 1, new_argv, Tcl_AppInit);
  return 0;
}

