#include "cool.h"
#include "string.h"
#include "netio.h"
#include "buf.h"
#include "netio_private.h"
#include "servers.h"

static const char *create_fail =
  "Either there is already a player with that name, or that name is illegal.\r\n";
static const char *connect_fail =
  "Either that player does not exist, or has a different password.\r\n";

static void check_connect (Player * p, char *msg);
static void stop_programming (Player * p);
static struct timeval msec2timeval (int msec);
static void player_command (Player * p, char *command);

void tell (Playerid player, const char *msg)
{
  Player *p;
  String *str;

  for (p = players; p; p = p->next) {
    if (p->connected && p->id == player) {
      str = string_cpy (msg);
      str = string_cat (str, "\r\n");
      buf_add (&p->output, str);
      break;
    }
  }
}

void boot (Playerid who)
{
  Player *p;

  for (p = players; p; p = p->next) {
    if (p->connected && p->id == who) {
      send (p->fd, DISCONNECT_MSG, strlen (DISCONNECT_MSG), 0);
      disconnect_player (p->id);
      remove_player (p);
      break;
    }
  }
}

void player_command (Player * p, char *command)
{
  if (!p->connected) {
    check_connect (p, command);
  } else if (p->isprogramming) {
    if (!strcmp (command, ".")) {
      stop_programming (p);
    } else {
      fprintf (p->progfile, "%s\n", command);
    }
  } else {
    while (isspace ((int)*command)) {
      command++;
    }
    p->parsing = 1;
    parse (p->id, command);
  }
}

void parse_done (Playerid player)
{
  Player *p;

  for (p = players; p; p = p->next) {
    if (p->id == player) {
      p->parsing = 0;
    }
  }
}

static Player *find_connect (Playerid player, Player * not)
{
  Player *p;

  for (p = players; p; p = p->next) {
    if (p != not && p->connected && p->id == player) {
      return p;
    }
  }
  return 0;
}

static void check_connect (Player * p, char *msg)
{
  char *command, *user, *password;
  Playerid player;
  Player *oldp;

  parse_connect (msg, &command, &user, &password);

  if (!strncmp (command, "co", 2)) {
    player = connect_player (user, password);
    if (player == NOTHING) {
      send (p->fd, connect_fail, strlen (connect_fail), 0);
      writelog ();
      fprintf (stderr, "Player failed connect %s on descriptor %d\n",
        user, p->fd);
    } else if ((oldp = find_connect (player, p))) {
      writelog ();
      fprintf (stderr, "Player %s(#%d) transferred from fd %d to fd %d\n",
        user, player, oldp->fd, p->fd);
      p->connected = 1;
      p->id = player;
      send (oldp->fd, TRANSFER_MSG1, strlen (TRANSFER_MSG1), 0);
      remove_player (oldp);
      send (p->fd, TRANSFER_MSG2, strlen (TRANSFER_MSG2), 0);
      send (p->fd, CONNECT_MSG, strlen (CONNECT_MSG), 0);
    } else {
      writelog ();
      fprintf (stderr, "Player connected %s(#%d) on descriptor %d\n",
        user, player, p->fd);
      p->connected = 1;
      p->id = player;
      send (p->fd, CONNECT_MSG, strlen (CONNECT_MSG), 0);
    }
  } else if (!strncmp (command, "cr", 2) && !registration) {
    player = create_player (user, password);
    if (player == NOTHING) {
      send (p->fd, create_fail, strlen (create_fail), 0);
      writelog ();
      fprintf (stderr, "Player failed create %s on descriptor %d\n",
        user, p->fd);
    } else {
      writelog ();
      fprintf (stderr, "Player created:  %s(#%d) on descriptor %d\n",
        user, player, p->fd);
      p->connected = 1;
      p->id = player;
      send (p->fd, CONNECT_MSG, strlen (CONNECT_MSG), 0);
    }
  } else {
    buf_add (&(p->output), string_cpy (welcome));
  }
}

void parse_connect (char *msg, char **command, char **user, char **pass)
{
  while (*msg && isascii (*msg) && isspace ((int)*msg))      /* skip whitespace */
    msg++;
  *command = msg;
  while (*msg && isascii (*msg) && !isspace ((int)*msg))     /* skip command */
    msg++;
  if (*msg) {
    *msg++ = '\0';              /* terminate cmd */
  }
  while (*msg && isascii (*msg) && isspace ((int)*msg))      /* skip whitespace */
    msg++;
  *user = msg;
  while (*msg && isascii (*msg) && !isspace ((int)*msg))     /* skip user */
    msg++;
  if (*msg) {
    *msg++ = '\0';              /* terminate user */
  }
  while (*msg && isascii (*msg) && isspace ((int)*msg))      /* skip whitespace */
    msg++;
  *pass = msg;
  while (*msg && isascii (*msg) && !isspace ((int)*msg))     /* skip password */
    msg++;
  *msg = '\0';                  /* terminate pass */
}

int start_programming (Playerid player, void **progwhat)
{
  Player *p;

  for (p = players; p; p = p->next) {
    if (p->id == player) {
      break;
    }
  }
  if (!p) {
    return -1;
  }
  sprintf (p->progfilename, "%s/%s_%d", PROGDIR, serv_id2name (0), player);
  if (!(p->progfile = fopen (p->progfilename, "wb"))) {
    writelog ();
    fprintf (stderr, "Couldn't open programming temp file ");
    perror (p->progfilename);
    p->progfilename[0] = '\0';
    return -2;
  } else {
    p->isprogramming = 1;
    p->progwhat = (void *) progwhat;
    return 0;
  }
}

static Playerid progr;          /* programmer */

static void stop_programming (Player * p)
{
  fclose (p->progfile);
  p->isprogramming = 0;
  if (!(p->progfile = fopen (p->progfilename, "rb"))) {
    writelog ();
    perror (p->progfilename);
  } else {
    progr = p->id;
    do_compile (p->id, p->progfile, p->progwhat);
    fclose (p->progfile);
    unlink (p->progfilename);
  }
  tell (p->id, "Exiting programming mode.");
  p->progfilename[0] = '\0';
  p->progfile = 0;
}

#define MSEC(T) (T.tv_sec * 1000 + T.tv_usec / 1000)

static struct timeval msec2timeval (int msec)
{
  struct timeval r;

  r.tv_sec = msec / 1000;
  r.tv_usec = (msec % 1000) * 1000;
  return r;
}

void queue_player_commands (struct timeval cur_time, struct timeval *timeout)
{
  Player *p;
  int msec_since_last;
  static struct timeval last = { 0, 0 };
  struct timeval new;

  msec_since_last = MSEC (cur_time) - MSEC (last);
  last = cur_time;
  for (p = players; p; p = p->next) {
    p->quota += msec_since_last;
    if (p->quota > MAX_CMDS * MSEC_PER_CMD) {
      p->quota = MAX_CMDS * MSEC_PER_CMD;
    }
    if (p->input.head && p->quota >= 0 && !p->parsing) {
      player_command (p, p->input.head->str->str);
      buf_delhead (&p->input);
      if (!p->isprogramming) {
        p->quota -= MSEC_PER_CMD;
      }
    }
    if (p->input.head && p->quota < 0) {
      /* return when quota is positive */
      new = msec2timeval (-p->quota);
      *timeout = (timercmp (&new, timeout, <))? new : *timeout;
    }
  }
}