/* obsproc.c
 *
 */

/*
    fics - An internet chess server.
    Copyright (C) 1993  Richard V. Nash

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

/* Revision history:
   name		email		yy/mm/dd	Change
   Dave Herscovici		95/11/26	Created
   Markus Uhlin			23/12/13	Fixed bugs
   Markus Uhlin			23/12/13	Reformatted functions
*/

#include "stdinclude.h"

#include <err.h>

#include "command.h"
#include "common.h"
#include "comproc.h"
#include "config.h"
#include "eco.h"
#include "ficsmain.h"
#include "formula.h"
#include "gamedb.h"
#include "gameproc.h"
#include "matchproc.h"
#include "movecheck.h"
#include "network.h"
#include "obsproc.h"
#include "playerdb.h"
#include "ratings.h"
#include "rmalloc.h"
#include "utils.h"

PUBLIC int GameNumFromParam(int p, int *p1, parameter *param)
{
  if (param->type == TYPE_WORD) {
    *p1 = player_find_part_login(param->val.word);
    if (*p1 < 0) {
      pprintf(p, "No user named \"%s\" is logged in.\n", param->val.word);
      return -1;
    }
    if (parray[*p1].game < 0)
      pprintf(p, "%s is not playing a game.\n", parray[*p1].name);
    return parray[*p1].game;
  } else {			/* Must be an integer */
    *p1 = -1;
    if (param->val.integer <= 0)
      pprintf(p, "%d is not a valid game number.\n", param->val.integer);
    return param->val.integer - 1;
  }
}

PRIVATE int gamesortfunc(const void *i, const void *j)
{
/* examine mode games moved to top of "games" output */
  return (GetRating(&parray[garray[*(int *) i].white], garray[*(int *) i].type) +
     GetRating(&parray[garray[*(int *) i].black], garray[*(int *) i].type) -
	  ((garray[*(int *) i].status == GAME_EXAMINE) ? 10000 : 0) -
     GetRating(&parray[garray[*(int *) j].white], garray[*(int *) j].type) -
     GetRating(&parray[garray[*(int *) j].black], garray[*(int *) j].type) +
	  ((garray[*(int *) j].status == GAME_EXAMINE) ? 10000 : 0));
}


PUBLIC int com_games(int p, param_list param)
{
  int i, j;
  int wp, bp;
  int ws, bs;
  int selected = 0;
  int count = 0;
  int totalcount;
  char *s = NULL;
  int slen = 0;
  int *sortedgames;		/* for qsort */

  totalcount = game_count();
  if (totalcount == 0) {
    pprintf(p, "There are no games in progress.\n");
  } else {
    sortedgames = rmalloc(totalcount * sizeof(int));	/* for qsort */

    if (param[0].type == TYPE_WORD) {
      s = param[0].val.word;
      slen = strlen(s);
      selected = atoi(s);
      if (selected < 0)
	selected = 0;
    }
    for (i = 0; i < g_num; i++) {
      if ((garray[i].status != GAME_ACTIVE) && (garray[i].status != GAME_EXAMINE))
	continue;
      if ((selected) && (selected != i + 1))
	continue;		/* not selected game number */
      wp = garray[i].white;
      bp = garray[i].black;
      if ((!selected) && s && strncasecmp(s, garray[i].white_name, slen) &&
	  strncasecmp(s, garray[i].black_name, slen))
	continue;		/* player names did not match */
      sortedgames[count++] = i;
    }
    if (!count)
      pprintf(p, "No matching games were found (of %d in progress).\n", totalcount);
    else {
      qsort(sortedgames, count, sizeof(int), gamesortfunc);
      pprintf(p, "\n");
      for (j = 0; j < count; j++) {
	i = sortedgames[j];
	wp = garray[i].white;
	bp = garray[i].black;
	board_calc_strength(&garray[i].game_state, &ws, &bs);
	if (garray[i].status != GAME_EXAMINE) {
	  pprintf_noformat(p, "%2d %4s %-11.11s %4s %-10.10s [%c%c%c%3d %3d] ",
			   i + 1,
			   ratstrii(GetRating(&parray[wp],
					      garray[i].type),
				    parray[wp].registered),
			   parray[wp].name,
			   ratstrii(GetRating(&parray[bp],
					      garray[i].type),
				    parray[bp].registered),
			   parray[bp].name,
			   (garray[i].private) ? 'p' : ' ',
			   *bstr[garray[i].type],
			   *rstr[garray[i].rated],
			   garray[i].wInitTime / 600,
			   garray[i].wIncrement / 10);
	  game_update_time(i);
	  pprintf_noformat(p, "%6s -",
		 tenth_str((garray[i].wTime > 0 ? garray[i].wTime : 0), 0));
	  pprintf_noformat(p, "%6s (%2d-%2d) %c: %2d\n",
		  tenth_str((garray[i].bTime > 0 ? garray[i].bTime : 0), 0),
			   ws, bs,
			 (garray[i].game_state.onMove == WHITE) ? 'W' : 'B',
			   garray[i].game_state.moveNum);
	} else {
	  pprintf_noformat(p, "%2d (Exam. %4d %-11.11s %4d %-10.10s) [%c%c%c%3d %3d] ",
			   i + 1,
			   garray[i].white_rating,
			   garray[i].white_name,
			   garray[i].black_rating,
			   garray[i].black_name,
			   (garray[i].private) ? 'p' : ' ',
			   *bstr[garray[i].type],
			   *rstr[garray[i].rated],
			   garray[i].wInitTime / 600,
			   garray[i].wIncrement / 10);
	  pprintf_noformat(p, "%c: %2d\n",
			 (garray[i].game_state.onMove == WHITE) ? 'W' : 'B',
			   garray[i].game_state.moveNum);
	}
      }
      if (count < totalcount)
	pprintf(p, "\n  %d game%s displayed (of %d in progress).\n", count,
		(count == 1) ? "" : "s", totalcount);
      else
	pprintf(p, "\n  %d game%s displayed.\n", totalcount, (totalcount == 1) ? "" : "s");
    }
    rfree(sortedgames);
  }
  return COM_OK;
}

PRIVATE int do_observe(int p, int obgame)
{
  if ((garray[obgame].private) && (parray[p].adminLevel < ADMIN_ADMIN)) {
    pprintf(p, "Sorry, game %d is a private game.\n", obgame + 1);
    return COM_OK;
  }
  if ((garray[obgame].white == p) || (garray[obgame].black == p)) {
    if (garray[obgame].status != GAME_EXAMINE) {
      pprintf(p, "You cannot observe a game that you are playing.\n");
      return COM_OK;
    }
  }
  if (player_is_observe(p, obgame)) {
    pprintf(p, "Removing game %d from observation list.\n", obgame + 1);
    player_remove_observe(p, obgame);
  } else {
    if (!player_add_observe(p, obgame)) {
      pprintf(p, "You are now observing game %d.\n", obgame + 1);
      send_board_to(obgame, p);
    } else {
      pprintf(p, "You are already observing the maximum number of games.\n");
    }
  }
  return COM_OK;
}

PUBLIC void unobserveAll(int p)
{
  int i;

  for (i = 0; i < parray[p].num_observe; i++) {
    pprintf(p, "Removing game %d from observation list.\n", parray[p].observe_list[i] + 1);
  }
  parray[p].num_observe = 0;
  return;
}

PUBLIC int com_unobserve(int p, param_list param)
{
  int gNum, p1;

  if (param[0].type == TYPE_NULL) {
    unobserveAll(p);
    return COM_OK;
  }
  gNum = GameNumFromParam(p, &p1, &param[0]);
  if (gNum < 0)
    return COM_OK;
  if (!player_is_observe(p, gNum)) {
    pprintf(p, "You are not observing game %d.\n", gNum);
  } else {
    player_remove_observe(p, gNum);
    pprintf(p, "Removing game %d from observation list.\n", gNum + 1);
  }
  return COM_OK;
}

PUBLIC int com_observe(int p, param_list param)
{
  int i;
  int p1, obgame;

  if ((parray[p].game >=0) &&(garray[parray[p].game].status == GAME_EXAMINE)) {
    pprintf(p, "You are still examining a game.\n");
    return COM_OK;
  }
  if (param[0].type == TYPE_NULL) {
    unobserveAll(p);
    return COM_OK;
  }
  obgame = GameNumFromParam(p, &p1, &param[0]);
  if (obgame < 0)
    return COM_OK;

  if ((obgame >= g_num) || ((garray[obgame].status != GAME_ACTIVE) &&
			    (garray[obgame].status != GAME_EXAMINE))) {
    pprintf(p, "There is no such game.\n");
    return COM_OK;
  }
  if ((p1 >= 0) && parray[p1].simul_info.numBoards) {
    for (i = 0; i < parray[p1].simul_info.numBoards; i++)
      if (parray[p1].simul_info.boards[i] >= 0)
	do_observe(p, parray[p1].simul_info.boards[i]);
  } else {
    do_observe(p, obgame);
  }
  return COM_OK;
}

PUBLIC int com_allobservers(int p, param_list param)
{
  int obgame;
  int p1;
  int start, end;
  int g;
  int first;

  if (param[0].type == TYPE_NULL) {
    obgame = -1;
  } else {
    obgame = GameNumFromParam(p, &p1, &param[0]);
    if (obgame < 0)
      return COM_OK;
  }
  if (obgame == -1) {
    start = 0;
    end = g_num;
  } else if ((obgame >= g_num) || ((obgame < g_num)
				   && ((garray[obgame].status != GAME_ACTIVE)
			     && (garray[obgame].status != GAME_EXAMINE)))) {
    pprintf(p, "There is no such game.\n");
    return COM_OK;
  } else {
    start = obgame;
    end = obgame + 1;
  }

  /* list games being played */

  for (g = start; g < end; g++) {
    if ((garray[g].status == GAME_ACTIVE) &&
	((parray[p].adminLevel > 0) || (garray[g].private == 0))) {
      for (first = 1, p1 = 0; p1 < p_num; p1++) {
	if ((parray[p1].status != PLAYER_EMPTY) && (player_is_observe(p1, g))) {
	  if (first) {
	    pprintf(p, "Observing %2d [%s vs. %s]:",
		    g + 1,
		    parray[garray[g].white].name,
		    parray[garray[g].black].name);
	    first = 0;
	  }
	  pprintf(p, " %s%s", (parray[p1].game >=0) ? "#" : "", parray[p1].name);
	}
      }
      if (!first)
	pprintf(p, "\n");
    }
  }

  /* list games being examined last */

  for (g = start; g < end; g++) {
    if ((garray[g].status == GAME_EXAMINE) &&
	((parray[p].adminLevel > 0) || (garray[g].private == 0))) {
      for (first = 1, p1 = 0; p1 < p_num; p1++) {
	if ((parray[p1].status != PLAYER_EMPTY) && (player_is_observe(p1, g) ||
						  (parray[p1].game == g))) {
	  if (first) {
	    if (strcmp(garray[g].white_name, garray[g].black_name)) {
	      pprintf(p, "Examining %2d [%s vs %s]:", g + 1,
		      garray[g].white_name, garray[g].black_name);
	    } else {
	      pprintf(p, "Examining %2d (scratch):", g + 1);
	    }
	    first = 0;
	  }
	  pprintf(p, " %s%s", (parray[p1].game == g) ? "#" : "", parray[p1].name);
	}
      }
      if (!first)
	pprintf(p, "\n");
    }
  }
  return COM_OK;
}

PUBLIC int com_unexamine(int p, param_list param)
{
  int g, p1, flag = 0;

  if ((parray[p].game <0) ||(garray[parray[p].game].status != GAME_EXAMINE)) {
    pprintf(p, "You are not examining any games.\n");
    return COM_OK;
  }
  g = parray[p].game;
  parray[p].game = -1;
  for (p1 = 0; p1 < p_num; p1++) {
    if (parray[p1].status != PLAYER_PROMPT)
      continue;
    if ((parray[p1].game == g) &&(p != p1)) {
      /* ok - there are other examiners to take over the game */
      flag = 1;
    }
    if ((player_is_observe(p1, g)) || (parray[p1].game == g)) {
      pprintf(p1, "%s stopped examining game %d.\n", parray[p].name, g + 1);
    }
  }
  if (!flag) {
    for (p1 = 0; p1 < p_num; p1++) {
      if (parray[p1].status != PLAYER_PROMPT)
	continue;
      if (player_is_observe(p1, g)) {
	pprintf(p1, "There are no examiners.\n");
	pcommand(p1, "unobserve %d", g + 1);
      }
    }
    game_remove(g);
  }
  pprintf(p, "You are no longer examining game %d.\n", g + 1);
  return COM_OK;
}

PUBLIC int com_mexamine(int p, param_list param)
{
  int g, p1, p2;

  if ((parray[p].game <0) ||(garray[parray[p].game].status != GAME_EXAMINE)) {
    pprintf(p, "You are not examining any games.\n");
    return COM_OK;
  }
  p1 = player_find_part_login(param[0].val.word);
  if (p1 < 0) {
    pprintf(p, "No user named \"%s\" is logged in.\n", param[0].val.word);
    return COM_OK;
  }
  g = parray[p].game;
  if (!player_is_observe(p1, g)) {
    pprintf(p, "%s must observe the game you are analysing.\n", parray[p1].name);
    return COM_OK;
  } else {
    if (parray[p1].game >=0) {
      pprintf(p, "%s is already analysing the game.\n", parray[p1].name);
      return COM_OK;
    }
    /* if we get here - let's make him examiner of the game */
    unobserveAll(p1);		/* fix for Xboard */
    player_decline_offers(p1, -1, PEND_MATCH);
    player_withdraw_offers(p1, -1, PEND_MATCH);
    player_withdraw_offers(p1, -1, PEND_SIMUL);

    parray[p1].game = g;	/* yep - it really is that easy :-) */
    pprintf(p1, "You are now examiner of game %d.\n", g + 1);
    send_board_to(g, p1);	/* pos not changed - but fixes Xboard */
    for (p2 = 0; p2 < p_num; p2++) {
      if (parray[p2].status != PLAYER_PROMPT)
	continue;
      if (p2 == p1)
	continue;
      if ((player_is_observe(p2, g)) || (parray[p2].game == g)) {
	pprintf_prompt(p2, "%s is now examiner of game %d.\n", parray[p1].name, g + 1);
      }
    }
  }
  return COM_OK;
}

PUBLIC int com_moves(int p, param_list param)
{
  int g;
  int p1;

  if (param[0].type == TYPE_NULL) {
    if (parray[p].game >=0) {
      g = parray[p].game;
    } else if (parray[p].num_observe) {
      for (g = 0; g < parray[p].num_observe; g++) {
	pprintf(p, "%s\n", movesToString(parray[p].observe_list[g], 0));
      }
      return COM_OK;
    } else {
      pprintf(p, "You are neither playing, observing nor examining a game.\n");
      return COM_OK;
    }
  } else {
    g = GameNumFromParam(p, &p1, &param[0]);
    if (g < 0)
      return COM_OK;
  }
  if ((g < 0) || (g >= g_num) || ((garray[g].status != GAME_ACTIVE) &&
				  (garray[g].status != GAME_EXAMINE))) {
    pprintf(p, "There is no such game.\n");
    return COM_OK;
  }
  if ((garray[g].white != p) && (garray[g].black != p) && (garray[g].private) && (parray[p].adminLevel < ADMIN_ADMIN)) {
    pprintf(p, "Sorry, that is a private game.\n");
    return COM_OK;
  }
  pprintf(p, "%s\n", movesToString(g, 0));	/* pgn may break interfaces? */
  return COM_OK;
}

PUBLIC int com_mailmoves(int p, param_list param)
{
  int g;
  int p1;
  char subj[81];

  if (!parray[p].registered) {
    pprintf (p,"Unregistered players cannot use mailmoves.\n");
    return COM_OK;
  }

  if (param[0].type == TYPE_NULL) {
    if (parray[p].game >=0) {
      g = parray[p].game;
    } else {
      pprintf(p, "You are neither playing, observing nor examining a game.\n");
      return COM_OK;
    }
  } else {
    g = GameNumFromParam(p, &p1, &param[0]);
    if (g < 0)
      return COM_OK;
  }
  if ((g < 0) || (g >= g_num) || ((garray[g].status != GAME_ACTIVE) && (garray[g].status != GAME_EXAMINE))) {
    pprintf(p, "There is no such game.\n");
    return COM_OK;
  }
  if ((garray[g].white != p) && (garray[g].black != p) && (garray[g].private) && (parray[p].adminLevel < ADMIN_ADMIN)) {
    pprintf(p, "Sorry, that is a private game.\n");
    return COM_OK;
  }
  sprintf(subj, "FICS game report %s vs %s", garray[g].white_name, garray[g].black_name);
  if (mail_string_to_user(p, subj, movesToString(g, parray[p].pgn))) {
    pprintf(p, "Moves NOT mailed, perhaps your address is incorrect.\n");
  } else {
    pprintf(p, "Moves mailed.\n");
  }
  return COM_OK;
}

PRIVATE int old_mail_moves(int p,int mail, param_list param)
{
  int p1, connected;
  int count;
  FILE *fp;
  char fname[MAX_FILENAME_SIZE];
  char tmp[2048];
  char *ptmp = tmp;

 if (mail && (!parray[p].registered)) {
    pprintf (p,"Unregistered players cannot use mailoldmoves.\n");
    return COM_OK;
  }

   if (param[0].type == TYPE_WORD) {
    if (!FindPlayer(p, param[0].val.word, &p1, &connected))
      return COM_OK;
  } else {
      p1 = p;
      connected = 1;
  }

  sprintf(fname, "%s/player_data/%c/%s.%s", stats_dir,
          parray[p1].login[0], parray[p1].login, STATS_GAMES);
  fp = fopen(fname, "r"); /* old moves now looks in history to save mem - DAV */

  if (!fp) {
    pprintf (p,"There is no old game for %s.\n", parray[p1].name);
    if (!connected)
      player_remove(p1);
    return COM_OK;
  }

  while (!feof(fp))
      fgets(tmp, 1024, fp);
  sscanf(ptmp, "%d", &count);
  fclose(fp); /* find the last game played in history */

  pprintf (p,"Last game for %s was history game %d.\n",parray[p1].name,count);

  if (mail)
   pcommand (p,"mailstored %s %d",parray[p1].name,count);
  else
   pcommand (p,"smoves %s %d",parray[p1].name,count);

  if (!connected)
    player_remove(p1);

  return COM_OK;
}

PUBLIC int com_oldmoves(int p, param_list param)
{
  return old_mail_moves(p , 0, param);
}

PUBLIC int com_mailoldmoves(int p, param_list param)
{
  return old_mail_moves(p , 1, param);
}

PUBLIC void
ExamineScratch(int p, param_list param)
{
	char	*val;
	char	 board[100];
	char	 category[100];
	char	 parsebuf[100];
	int	 confused = 0;
	int	 g = game_new();

	unobserveAll(p);

	player_decline_offers(p, -1, PEND_MATCH);
	player_withdraw_offers(p, -1, PEND_MATCH);
	player_withdraw_offers(p, -1, PEND_SIMUL);

	garray[g].bInitTime = garray[g].bIncrement = 0;
	garray[g].wInitTime = garray[g].wIncrement = 0;

	garray[g].startTime		= tenth_secs();
	garray[g].timeOfStart		= tenth_secs();

	garray[g].clockStopped		= 0;
	garray[g].lastDecTime		= garray[g].startTime;
	garray[g].lastMoveTime		= garray[g].startTime;
	garray[g].rated			= 0;
	garray[g].status		= GAME_EXAMINE;
	garray[g].totalHalfMoves	= 0;
	garray[g].type			= TYPE_UNTIMED;
	garray[g].wTime			= garray[g].bTime = 0;
	garray[g].white			= garray[g].black = p;
	parray[p].game			= g;
	parray[p].side			= WHITE;

	board[0]	= '\0';
	category[0]	= '\0';

	if (param[0].val.string != parray[p].name &&
	    param[1].type == TYPE_WORD) {
		strcpy(category, param[0].val.string);
		strcpy(board, param[1].val.string);
	} else if (param[1].type != TYPE_NULL) {
		val = param[1].val.string;

		while (!confused && sscanf(val, " %99s", parsebuf) == 1) {
			val = eatword(eatwhite(val));

			if (category[0] != '\0' && board[0] == '\0') {
				strcpy(board, parsebuf);
			} else if (isdigit(*parsebuf)) {
				pprintf(p, "You can't specify time controls."
				    "\n");
				return;
			} else if (category[0] == '\0') {
				strcpy(category, parsebuf);
			} else {
				confused = 1;
			}
		}

		if (confused) {
			pprintf(p, "Can't interpret %s in match command.\n",
			    parsebuf);
			return;
		}
	}

	if (category[0] && !board[0]) {
		pprintf(p, "You must specify a board and a category.\n");
		return;
	}

	pprintf(p, "Starting a game in examine (scratch) mode.\n");

	if (category[0]) {
		pprintf(p, "Loading from catagory: %s, board: %s.\n",
		    category,
		    board);
	}
	if (board_init(&garray[g].game_state, category, board)) {
		pprintf(p, "PROBLEM LOADING BOARD. Game Aborted.\n");
		fprintf(stderr, "FICS: PROBLEM LOADING BOARD. Game Aborted.\n");
		return;
	}

	garray[g].game_state.gameNum = g;
	strcpy(garray[g].white_name, parray[p].name);
	strcpy(garray[g].black_name, parray[p].name);
	garray[g].white_rating = garray[g].black_rating = parray[p].s_stats.rating;
	send_boards(g);
	MakeFENpos(g, (char *)garray[g].FENstartPos);
}

PRIVATE int
ExamineStored(FILE *fp, int p, char *filename)
{
	char	 board[100];
	char	 category[100];
	game	*gg;
	int	 g;

	unobserveAll(p);

	player_decline_offers(p, -1, PEND_MATCH);
	player_withdraw_offers(p, -1, PEND_MATCH);
	player_withdraw_offers(p, -1, PEND_SIMUL);

	g	= game_new();
	gg	= &garray[g];

	board[0]	= '\0';
	category[0]	= '\0';

	if (board_init(&gg->game_state, category, board)) {
		pprintf(p, "PROBLEM LOADING BOARD. Game Aborted.\n");
		fprintf(stderr, "FICS: PROBLEM LOADING BOARD %s %s. "
		    "Game Aborted.\n", category, board);
		return -1;
	}

	gg->status = GAME_EXAMINE;

	if (ReadGameAttrs(fp, filename, g) < 0) {
		pprintf(p, "Gamefile is corrupt; please notify an admin.\n");
		return -1;
	}

	gg->startTime		= tenth_secs();
	gg->lastDecTime		= gg->startTime;
	gg->lastMoveTime	= gg->startTime;

	gg->totalHalfMoves	= gg->numHalfMoves;
	gg->numHalfMoves	= 0;
	gg->revertHalfMove	= 0;

	gg->black		= p;
	gg->white		= p;
	gg->game_state.gameNum	= g;

	parray[p].game = g;
	parray[p].side = WHITE;

	send_boards(g);
	MakeFENpos(g, (char *)garray[g].FENstartPos);

	return g;
}

PRIVATE void ExamineAdjourned(int p, int p1, int p2)
{
  FILE *fp;
  char filename[1024];
  char *p1Login, *p2Login;
  int g;

  p1Login = parray[p1].login;
  p2Login = parray[p2].login;

  sprintf(filename, "%s/%c/%s-%s", adj_dir, *p1Login, p1Login, p2Login);
  fp = fopen(filename, "r");
  if (!fp) {
    sprintf(filename, "%s/%c/%s-%s", adj_dir, *p2Login, p1Login, p2Login);
    fp = fopen(filename, "r");
    if (!fp) {
      sprintf(filename, "%s/%c/%s-%s", adj_dir, *p2Login, p2Login, p1Login);
      fp = fopen(filename, "r");
      if (!fp) {
	sprintf(filename, "%s/%c/%s-%s", adj_dir, *p1Login, p2Login, p1Login);
	fp = fopen(filename, "r");
	if (!fp) {
	  pprintf(p, "No stored game between \"%s\" and \"%s\".\n",
		  parray[p1].name, parray[p2].name);
	  return;
	}
      }
    }
  }
  g = ExamineStored(fp, p, filename);
  fclose(fp);

  if (g >= 0) {
    if (garray[g].white_name[0] == '\0')
      strcpy(garray[g].white_name, p1Login);
    if (garray[g].black_name[0] == '\0')
      strcpy(garray[g].black_name, p2Login);
  }
  return;
}

PRIVATE char *FindHistory(int p, int p1, int game)
{
  FILE *fpHist;
  static char fileName[MAX_FILENAME_SIZE];
  int index;
  long when;

  sprintf(fileName, "%s/player_data/%c/%s.%s", stats_dir,
	  parray[p1].login[0], parray[p1].login, STATS_GAMES);
  fpHist = fopen(fileName, "r");
  if (fpHist == NULL) {
    pprintf(p, "No games in history for %s.\n", parray[p1].name);
    return(NULL);
  }
  do {
    fscanf(fpHist, "%d %*c %*d %*c %*d %*s %*s %*d %*d %*d %*d %*s %*s %ld",
	   &index, &when);
  } while (!feof(fpHist) && index != game);

  if (feof(fpHist)) {
    pprintf(p, "There is no history game %d for %s.\n", game, parray[p1].name);
    fclose(fpHist);
    return(NULL);
  }
  fclose(fpHist);

  sprintf(fileName, "%s/%ld/%ld", hist_dir, when % 100, when);
  return(fileName);
}

/* I want to know how game ended */

PRIVATE char *FindHistory2(int p, int p1,int game,char* End)
{
  FILE *fpHist;
  static char fileName[MAX_FILENAME_SIZE];
  int index;
  long when;

  sprintf(fileName, "%s/player_data/%c/%s.%s", stats_dir,
          parray[p1].login[0], parray[p1].login, STATS_GAMES);
  fpHist = fopen(fileName, "r");
  if (fpHist == NULL) {
    pprintf(p, "No games in history for %s.\n", parray[p1].name);
    return(NULL);
  }
  do {
    fscanf(fpHist, "%d %*c %*d %*c %*d %*s %*s %*d %*d %*d %*d %*s %s %ld",
	   &index, End, &when);
  } while (!feof(fpHist) && index != game);

  if (feof(fpHist)) {
    pprintf(p, "There is no history game %d for %s.\n", game, parray[p1].name);
    fclose(fpHist);
    return(NULL);
  }
  fclose(fpHist);

  sprintf(fileName, "%s/%ld/%ld", hist_dir, when % 100, when);
  return(fileName);
}

PRIVATE void ExamineHistory(int p, int p1, int game)
{
  char *fileName;

  fileName = FindHistory(p, p1, game);
  if (fileName != NULL) {
    FILE *fpGame = fopen(fileName, "r");
    if (fpGame == NULL) {
      pprintf(p, "History game %d not available for %s.\n", game, parray[p1].name);
    } else {
      ExamineStored(fpGame, p, fileName);
      fclose(fpGame);
    }
  }
  return;
}

PRIVATE void ExamineJournal(int p,int p1,char slot)
{
  char fname[MAX_FILENAME_SIZE];
  char* name_from = parray[p1].login;
  FILE *fpGame;

  if ((parray[p1].jprivate) && (p != p1) && (parray[p].adminLevel < ADMIN_ADMIN)) {
    pprintf (p,"Sorry, this journal is private.\n");
    return;
  }
  if (((slot - 'a' - 1) > MAX_JOURNAL) && (parray[p1].adminLevel < ADMIN_ADMIN)
&& (!titled_player(p,parray[p1].login))) {
    pprintf (p,"%s's maximum journal entry is %c\n",parray[p1].name,toupper((char)(MAX_JOURNAL + 'A' - 1)));
    return;
  }

  sprintf(fname, "%s/%c/%s.%c", journal_dir, name_from[0],name_from,slot);
  fpGame = fopen(fname, "r");
    if (fpGame == NULL) {
      pprintf(p, "Journal entry %c is not available for %s.\n", toupper (slot), parray[p1].name);
    } else {
      ExamineStored(fpGame, p, fname);
      fclose(fpGame);
    }
  return;
}

PUBLIC int com_examine(int p, param_list param)
{
  int p1, p2 = p, p1conn, p2conn = 1;
  char* param2string;
  char fname[MAX_FILENAME_SIZE];

  if ((parray[p].game >=0) &&(garray[parray[p].game].status == GAME_EXAMINE)) {
    pprintf(p, "You are already examining a game.\n");
  } else if (parray[p].game >=0) {
    pprintf(p, "You are playing a game.\n");
  } else if (param[0].type == TYPE_NULL) {
    ExamineScratch(p, param);
  } else if (param[0].type == TYPE_WORD) {
      if (param[1].type == TYPE_WORD) {
        sprintf(fname, "%s/%s/%s", board_dir, param[0].val.word, param[1].val.word);
        if (file_exists(fname)) {
          ExamineScratch(p, param);
          return COM_OK;
        }
      }
      if (!FindPlayer(p, param[0].val.word, &p1, &p1conn))
        return COM_OK;

    if (param[1].type == TYPE_INT)
      ExamineHistory(p, p1, param[1].val.integer);
    else {
      if (param[1].type == TYPE_WORD) {

        /* Lets check the journal */
        param2string = param[1].val.word;
        if ((strlen(param2string) == 1) && (isalpha(param2string[0]))) {
          ExamineJournal(p,p1,param2string[0]);
          if (!p1conn)
            player_remove(p1);
          return COM_OK;
        } else {
          if (!FindPlayer(p, param[1].val.word, &p2, &p2conn)) {
            if (!p1conn)
              player_remove(p1);
            return COM_OK;
          }
        }
      }
      ExamineAdjourned(p, p1, p2);
      if (!p2conn)
       player_remove(p2);
    }
    if (!p1conn)
     player_remove(p1);
  }
  return COM_OK;
}

PUBLIC int com_stored(int p, param_list param)
{
  DIR *dirp;
#ifdef USE_DIRENT
  struct dirent *dp;
#else
  struct direct *dp;
#endif
  int p1, connected;
  char dname[MAX_FILENAME_SIZE];

  if (param[0].type == TYPE_WORD) {
    if (!FindPlayer(p, param[0].val.word, &p1, &connected))
      return COM_OK;
  } else {
      p1 = p;
      connected = 1;
  }

  sprintf(dname, "%s/%c", adj_dir, parray[p1].login[0]);
  dirp = opendir(dname);
  if (!dirp) {
    pprintf(p, "Player %s has no games stored.\n", parray[p1].name);
    if (!connected)
      player_remove(p1);
    return COM_OK;
  }
  pprintf(p, "Stored games for %s:\n", parray[p1].name);
  for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
    if (file_has_pname(dp->d_name, parray[p1].login)) {
      pprintf(p, "   %s vs. %s\n", file_wplayer(dp->d_name), file_bplayer(dp->d_name));
    }
  }

  closedir(dirp);
  pprintf(p, "\n");
  if (!connected)
    player_remove(p1);
  return COM_OK;
}

PRIVATE void
stored_mail_moves(int p, int mail, param_list param)
{
	FILE	*fpGame;
	char	*fileName;
	char	*name_from;
	char	*param2string = NULL;
	char	 fileName2[MAX_FILENAME_SIZE];
	int	 g = -1;
	int	 wp, wconnected, bp, bconnected, gotit = 0;

	if (mail && !parray[p].registered) {
		pprintf(p, "Unregistered players cannot use mailstored.\n");
		return;
	}

	if (!FindPlayer(p, param[0].val.word, &wp, &wconnected))
		return;

	if (param[1].type == TYPE_INT) { /* look for a game from history */
		if ((fileName = FindHistory(p, wp, param[1].val.integer)) !=
		    NULL) {
			if ((fpGame = fopen(fileName, "r")) == NULL) {
				pprintf(p, "History game %d not available "
				    "for %s.\n",
				    param[1].val.integer,
				    parray[wp].name);
			} else {
				g = game_new();

				if (ReadGameAttrs(fpGame, fileName, g) < 0)
					pprintf(p, "Gamefile is corrupt; "
					    "please notify an admin.\n");
				else
					gotit = 1;

				fclose(fpGame);
			}
		}
	} else {
		/*
		 * Let's test for journal
		 */
		name_from = param[0].val.word;
		param2string = param[1].val.word;

		if (strlen(param2string) == 1 && isalpha(param2string[0])) {
			if (parray[wp].jprivate &&
			    parray[p].adminLevel < ADMIN_ADMIN &&
			    p != wp) {
				pprintf(p, "Sorry, the journal from which you "
				    "are trying to fetch is private.\n");
			} else {
				if ((param2string[0] - 'a' - 1) > MAX_JOURNAL &&
				    parray[wp].adminLevel < ADMIN_ADMIN &&
				    !titled_player(p, parray[wp].login)) {
					pprintf(p, "%s's maximum journal entry "
					    "is %c\n",
					    parray[wp].name,
					    toupper((char)(MAX_JOURNAL +
					    'A' - 1)));
				} else {
					sprintf(fileName2, "%s/%c/%s.%c",
					    journal_dir,
					    name_from[0],
					    name_from,
					    param2string[0]);

					if ((fpGame = fopen(fileName2, "r")) ==
					    NULL) {
						pprintf(p, "Journal entry %c "
						    "is not available for %s.\n",
						    toupper(param2string[0]),
						    parray[wp].name);
					} else {
						g = game_new();

						/* XXX: was 'fileName' */
						if (ReadGameAttrs(fpGame,
						    fileName2, g) < 0)
							pprintf(p, "Journal "
							    "entry is corrupt; "
							    "please notify an "
							    "admin.\n");
						else
							gotit = 1;

						fclose(fpGame);
					}
				}
			}
		} else {
			/*
			 * look for a stored game between the players
			 */

			if (FindPlayer(p, param[1].val.word, &bp,
			    &bconnected)) {
				g = game_new();

				if (game_read(g, wp, bp) >= 0) {
					gotit = 1;
				} else if (game_read(g, bp, wp) >= 0) {
					gotit = 1;
				} else {
					pprintf(p, "There is no stored game "
					    "%s vs. %s\n",
					    parray[wp].name,
					    parray[bp].name);
				}

				if (!bconnected)
					player_remove(bp);
			}
		}
	}

	if (gotit) {
		if (strcasecmp(parray[p].name, garray[g].white_name) &&
		    strcasecmp(parray[p].name, garray[g].black_name) &&
		    garray[g].private &&
		    parray[p].adminLevel < ADMIN_ADMIN) {
			pprintf(p, "Sorry, that is a private game.\n");
		} else {
			if (mail == 1) { /* Do mailstored */
				char	subj[81];

				if (param[1].type == TYPE_INT) {
					sprintf(subj, "FICS history game: "
					    "%s %d",
					    parray[wp].name,
					    param[1].val.integer);
				} else {
					if (param2string == NULL) /* XXX */
						errx(1, "%s: param2string == "
						    "NULL", __func__);
					if (strlen(param2string) == 1 &&
					    isalpha(param2string[0])) {
						sprintf(subj, "FICS journal "
						    "game %s vs %s",
						    garray[g].white_name,
						    garray[g].black_name);
					} else {
						sprintf(subj, "FICS adjourned "
						    "game %s vs %s",
						    garray[g].white_name,
						    garray[g].black_name);
					}
				}
				if (mail_string_to_user(p, subj, movesToString
				    (g, parray[p].pgn)))
					pprintf(p, "Moves NOT mailed, perhaps "
					    "your address is incorrect.\n");
				else
					pprintf(p, "Moves mailed.\n");
			} else {
				pprintf(p, "%s\n", movesToString(g, 0));
			} /* Do smoves */
		}
	}

	if (!wconnected)
		player_remove(wp);
	if (g != -1)
		game_remove(g);
}

/* Tidied up a bit but still messy */

PUBLIC int com_mailstored(int p, param_list param)
{
  stored_mail_moves(p, 1, param);
  return COM_OK;
}

PUBLIC int com_smoves(int p, param_list param)
{
  stored_mail_moves(p, 0, param);
  return COM_OK;
}

PUBLIC int com_sposition(int p, param_list param)
{
  int wp, wconnected, bp, bconnected, confused = 0;
  int g;

  if (!FindPlayer(p, param[0].val.word, &wp, &wconnected))
    return (COM_OK);
  if (!FindPlayer(p, param[1].val.word, &bp, &bconnected)) {
    if (!wconnected)
      player_remove(wp);
    return (COM_OK);
  }

  g = game_new();
  if (game_read(g, wp, bp) < 0) {	/* if no game white-black, */
    if (game_read(g, bp, wp) < 0) {	/* look for black-white */
      confused = 1;
      pprintf(p, "There is no stored game %s vs. %s\n", parray[wp].name, parray[bp].name);
    } else {
      int tmp;
      tmp = wp;
      wp = bp;
      bp = tmp;
      tmp = wconnected;
      wconnected = bconnected;
      bconnected = tmp;
    }
  }
  if (!confused) {
    if ((wp != p) && (bp != p) && (garray[g].private) && (parray[p].adminLevel < ADMIN_ADMIN)) {
      pprintf(p, "Sorry, that is a private game.\n");
    } else {
      garray[g].white = wp;
      garray[g].black = bp;
      garray[g].startTime = tenth_secs();
      garray[g].lastMoveTime = garray[g].startTime;
      garray[g].lastDecTime = garray[g].startTime;
      pprintf(p, "Position of stored game %s vs. %s\n", parray[wp].name, parray[bp].name);
      send_board_to(g, p);
    }
  }
  game_remove(g);
  if (!wconnected)
    player_remove(wp);
  if (!bconnected)
    player_remove(bp);
  return COM_OK;
}

PUBLIC int com_forward(int p, param_list param)
{
  int nHalfMoves = 1;
  int g, i;
  int p1;
  unsigned now;

  if (!((parray[p].game >=0) &&(garray[parray[p].game].status == GAME_EXAMINE))) {
    pprintf(p, "You are not examining any games.\n");
    return COM_OK;
  }
  g = parray[p].game;
  if (!strcmp(garray[g].white_name, garray[g].black_name)) {
    pprintf(p, "You cannot go forward; no moves are stored.\n");
    return COM_OK;
  }
  if (param[0].type == TYPE_INT) {
    nHalfMoves = param[0].val.integer;
  }
  if (garray[g].numHalfMoves > garray[g].revertHalfMove) {
    pprintf(p, "No more moves.\n");
    return COM_OK;
  }
  if (garray[g].numHalfMoves < garray[g].totalHalfMoves) {
    for (p1 = 0; p1 < p_num; p1++) {
      if (parray[p1].status != PLAYER_PROMPT)
	continue;
      if (player_is_observe(p1, g) || parray[p1].game == g) {
	pprintf(p1, "%s goes forward %d move%s.\n",
		parray[p].name, nHalfMoves, (nHalfMoves == 1) ? "" : "s");
      }
    }
  }
  for (i = 0; i < nHalfMoves; i++) {
    if (garray[g].numHalfMoves < garray[g].totalHalfMoves) {
      execute_move(&garray[g].game_state, &garray[g].moveList[garray[g].numHalfMoves], 1);
      if (garray[g].numHalfMoves + 1 > garray[g].examMoveListSize) {
	garray[g].examMoveListSize += 20;	/* Allocate 20 moves at a
						   time */
	if (!garray[g].examMoveList) {
	  garray[g].examMoveList = (move_t *) rmalloc(sizeof(move_t) * garray[g].examMoveListSize);
	} else {
	  garray[g].examMoveList = (move_t *) rrealloc(garray[g].examMoveList, sizeof(move_t) * garray[g].examMoveListSize);
	}
      }
      garray[g].examMoveList[garray[g].numHalfMoves] = garray[g].moveList[garray[g].numHalfMoves];
      garray[g].revertHalfMove++;
      garray[g].numHalfMoves++;
    } else {
      for (p1 = 0; p1 < p_num; p1++) {
	if (parray[p1].status != PLAYER_PROMPT)
	  continue;
	if (player_is_observe(p1, g) || parray[p1].game == g) {
	  pprintf(p1, "End of game.\n");
	}
      }
      break;
    }
  }
  /* roll back time */
  if (garray[g].game_state.onMove == WHITE) {
    garray[g].wTime += (garray[g].lastDecTime - garray[g].lastMoveTime);
  } else {
    garray[g].bTime += (garray[g].lastDecTime - garray[g].lastMoveTime);
  }
  now = tenth_secs();
  if (garray[g].numHalfMoves == 0)
    garray[g].timeOfStart = now;
  garray[g].lastMoveTime = now;
  garray[g].lastDecTime = now;
  send_boards(g);
  return COM_OK;
}

PUBLIC int com_backward(int p, param_list param)
{
  int nHalfMoves = 1;
  int g, i;
  int p1;
  unsigned now;

  if (!((parray[p].game >=0) &&(garray[parray[p].game].status == GAME_EXAMINE))) {
    pprintf(p, "You are not examining any games.\n");
    return COM_OK;
  }
  g = parray[p].game;
  if (param[0].type == TYPE_INT) {
    nHalfMoves = param[0].val.integer;
  }
  if (garray[g].numHalfMoves != 0) {
    for (p1 = 0; p1 < p_num; p1++) {
      if (parray[p1].status != PLAYER_PROMPT)
	continue;
      if (player_is_observe(p1, g) || parray[p1].game == g) {
	pprintf(p1, "%s backs up %d move%s.\n",
		parray[p].name, nHalfMoves, (nHalfMoves == 1) ? "" : "s");
      }
    }
  }
  for (i = 0; i < nHalfMoves; i++) {
    if (backup_move(g, REL_EXAMINE) != MOVE_OK) {
      for (p1 = 0; p1 < p_num; p1++) {
	if (parray[p1].status != PLAYER_PROMPT)
	  continue;
	if (player_is_observe(p1, g) || parray[p1].game == g) {
	  pprintf(p1, "Beginning of game.\n");
	}
      }

      break;
    }
  }
  if (garray[g].numHalfMoves < garray[g].revertHalfMove) {
    garray[g].revertHalfMove = garray[g].numHalfMoves;
  }
  /* roll back time */
  if (garray[g].game_state.onMove == WHITE) {
    garray[g].wTime += (garray[g].lastDecTime - garray[g].lastMoveTime);
  } else {
    garray[g].bTime += (garray[g].lastDecTime - garray[g].lastMoveTime);
  }
  now = tenth_secs();
  if (garray[g].numHalfMoves == 0)
    garray[g].timeOfStart = now;
  garray[g].lastMoveTime = now;
  garray[g].lastDecTime = now;
  send_boards(g);
  return COM_OK;
}

PUBLIC int com_revert(int p, param_list param)
{
  int nHalfMoves = 1;
  int g, i;
  int p1;
  unsigned now;

  if (!((parray[p].game >=0) &&(garray[parray[p].game].status == GAME_EXAMINE))) {
    pprintf(p, "You are not examining any games.\n");
    return COM_OK;
  }
  g = parray[p].game;
  nHalfMoves = garray[g].numHalfMoves - garray[g].revertHalfMove;
  if (nHalfMoves == 0) {
    pprintf(p, "Already at mainline.\n");
    return COM_OK;
  }
  if (nHalfMoves < 0) {		/* eek - should NEVER happen! */
    fprintf(stderr, "OUCH! in com_revert: nHalfMoves < 0\n");
    return COM_OK;
  }
  for (p1 = 0; p1 < p_num; p1++) {
    if (parray[p1].status != PLAYER_PROMPT)
      continue;
    if (player_is_observe(p1, g) || parray[p1].game == g) {
      pprintf(p1, "%s reverts to mainline.\n", parray[p].name);
    }
  }
  for (i = 0; i < nHalfMoves; i++) {
    backup_move(g, REL_EXAMINE);/* should never return error */
  }
  /* roll back time */
  if (garray[g].game_state.onMove == WHITE) {
    garray[g].wTime += (garray[g].lastDecTime - garray[g].lastMoveTime);
  } else {
    garray[g].bTime += (garray[g].lastDecTime - garray[g].lastMoveTime);
  }
  now = tenth_secs();
  if (garray[g].numHalfMoves == 0)
    garray[g].timeOfStart = now;
  garray[g].lastMoveTime = now;
  garray[g].lastDecTime = now;
  send_boards(g);
  return COM_OK;
}

PUBLIC int com_history(int p, param_list param)
{
  int p1, connected;
  char fname[MAX_FILENAME_SIZE];

  if (param[0].type == TYPE_WORD) {
    if (!FindPlayer(p, param[0].val.word, &p1, &connected))
      return COM_OK;
  } else {
      p1 = p;
      connected = 1;
  }

  sprintf(fname, "%s/player_data/%c/%s.%s", stats_dir, parray[p1].login[0],
          parray[p1].login, STATS_GAMES);
  pgames(p, p1, fname);
  if (!connected)
    player_remove(p1);
  return COM_OK;
}

PUBLIC int com_journal(int p, param_list param)
{
  int p1, connected;
  char fname[MAX_FILENAME_SIZE];

    if (param[0].type == TYPE_WORD) {
    if (!FindPlayer(p, param[0].val.word, &p1, &connected))
      return COM_OK;
  } else {
      p1 = p;
      connected = 1;
  }

if (!parray[p1].registered) {
    pprintf (p,"Only registered players may keep a journal.\n");
    if (!connected)
      player_remove(p1);
    return COM_OK;
  }
  if ((parray[p1].jprivate) && (p != p1) && (parray[p].adminLevel < ADMIN_ADMIN)) {
    pprintf (p,"Sorry, this journal is private.\n");
    if (!connected)
      player_remove(p1);
    return COM_OK;
  }
  sprintf(fname, "%s/player_data/%c/%s.%s", stats_dir, parray[p1].login[0],
	  parray[p1].login, STATS_JOURNAL);
  pjournal(p, p1, fname);
  if (!connected)
    player_remove(p1);
  return COM_OK;
}

PRIVATE void jsave_journalentry(int p,char save_spot,int p1,char from_spot,char* to_file)

{
  FILE *Game;

  char fname[MAX_FILENAME_SIZE], fname2[MAX_FILENAME_SIZE];
  char command[MAX_FILENAME_SIZE*2+3];
  char* name_from = parray[p1].login;
  char* name_to = parray[p].login;
  char WhiteName[MAX_LOGIN_NAME + 1];
  char BlackName[MAX_LOGIN_NAME + 1];
  int  WhiteRating;
  int  BlackRating;
  int  i,t;
  char type[100];
  char eco[100];
  char ending[100];
  char result[100];

  sprintf(fname, "%s/%c/%s.%c", journal_dir, name_from[0],name_from,from_spot);
  Game = fopen(fname, "r");
  if (Game == NULL) {
     pprintf(p, "Journal entry %c not available for %s.\n", toupper(from_spot), parray[p1].name);
     return;
     }
  fclose (Game);

  sprintf(fname2, "%s/%c/%s.%c", journal_dir, name_to[0],name_to,save_spot);
  unlink (fname2); /* necessarity if cp is hard aliased to cp -i */
  sprintf(command, "cp %s %s",fname,fname2);

  if (system(command)) { /* A little messy, but works */
     pprintf (p,"System command in jsave_journalentry failed!\n");
     pprintf (p,"Please report this to an admin.\n");
     fprintf (stderr, "FICS: System command failed in jsave_journalentry\n");
     return;
     }

 sprintf(fname, "%s/player_data/%c/%s.%s", stats_dir, name_to[0],
          name_to, STATS_JOURNAL);

 if (!journal_get_info(p,from_spot,WhiteName,&WhiteRating,
 BlackName,&BlackRating,type,&t,&i,eco,
 ending,result,fname)) {
   return;
 }

 addjournalitem(p, toupper(save_spot), WhiteName, WhiteRating,
  BlackName,BlackRating,type,t,i,eco,
  ending,result, to_file);

 pprintf(p,"Journal entry %s %c saved in slot %c in journal.\n",parray[p1].name, toupper(from_spot), toupper(save_spot));
}

PUBLIC void jsave_history(int p,char save_spot,int p1,int from,char* to_file)
{


 char End[100];
 char jfname[MAX_FILENAME_SIZE];
 char* HistoryFname = FindHistory2(p, p1, from, End);
     /* End symbol Mat Res, etc is the only thing we can't find out */
 char command[MAX_FILENAME_SIZE*2+3];
 char* name_to = parray[p].login;
 char* EndSymbol;
 FILE *Game;
 char type[4];
 int g;
 char* filename[MAX_FILENAME_SIZE+1];

  if (HistoryFname != NULL) {
    Game = fopen(HistoryFname, "r");
    if (Game == NULL) {
      pprintf(p, "History game %d not available for %s.\n", from, parray[p1].name);
    } else {
      sprintf(jfname, "%s/%c/%s.%c", journal_dir, name_to[0],name_to,save_spot);
      unlink(jfname); /* necessary if cp is hard aliased to cp -i */
      sprintf(command, "cp %s %s",HistoryFname,jfname);
    if (system(command)) { /* A little messy, but works */
       pprintf (p,"System command in jsave_history failed!\n");
       pprintf (p,"Please report this to an admin.\n");
       fprintf (stderr, "FICS: System command failed in jsave_journalentry\n");
       return;
       }
    g = game_new(); /* Open a dummy game */

    if (ReadGameAttrs(Game, filename, g) < 0) {
      pprintf (p,"Gamefile is corrupt. Please tell an admin.\n");
      game_free(g);
      fclose (Game);
      return;
    }
    fclose (Game);
    if (garray[g].private) {
      type[0] = 'p';
    } else {
      type[0] = ' ';
    }
    if (garray[g].type == TYPE_BLITZ) {
      type[1] = 'b';
    } else if (garray[g].type == TYPE_WILD) {
      type[1] = 'w';
    } else if (garray[g].type == TYPE_STAND) {
      type[1] = 's';
    } else {
      if (garray[g].type == TYPE_NONSTANDARD)
        type[1] = 'n';
      else
        type[1] = 'u';
    }
    if (garray[g].rated) {
      type[2] = 'r';
    } else {
      type[2] = 'u';
    }
    type[3] = '\0';

   EndSymbol = EndSym(g);
   addjournalitem(p, toupper(save_spot), garray[g].white_name, garray[g].white_rating,
  garray[g].black_name,garray[g].black_rating,type,garray[g].wInitTime,garray[g].wIncrement,getECO(g),
   End,EndSymbol, to_file);
   game_free(g);
   pprintf(p,"Game %s %d saved in slot %c in journal.\n",parray[p1].name, from, toupper(save_spot));
   }
  }
}

PUBLIC int com_jsave(int p, param_list param)
{
  int p1, p1conn;
  char* to = param[0].val.word;
  char* from;
  char fname[MAX_FILENAME_SIZE];

  if (!parray[p].registered) {
     pprintf (p,"Only registered players may keep a journal.\n");
     return COM_OK;
  }

  if ((strlen(to) != 1) || (!(isalpha(to[0])))) {
    pprintf (p,"Journal entries are referenced by single letters.\n");
    return COM_OK;
  }

  if (((to[0] - 'a' - 1) > MAX_JOURNAL) && (parray[p].adminLevel < ADMIN_ADMIN) && (!titled_player(p,parray[p].login))) {
    pprintf (p,"Your maximum journal entry is %c\n",toupper ((char)(MAX_JOURNAL + 'A' - 1)));
    return COM_OK;
  }

  if (!FindPlayer(p, param[1].val.word, &p1, &p1conn))
    return COM_OK;

  if (param[2].type == TYPE_INT) {

  /* grab from a history */
    sprintf (fname,"%s/player_data/%c/%s.%s",stats_dir,parray[p].login[0],parray[p].login, STATS_JOURNAL);
    jsave_history(p, to[0], p1, param[2].val.integer,fname);

  } else {

  from = param[2].val.word;

  if ((strlen(from) != 1) || (!(isalpha(from[0])))) {
    pprintf (p,"Journal entries are referenced by single letters.\n");
    if (!p1conn)
      player_remove(p1);
    return COM_OK;
    }

  if ((parray[p1].jprivate) && (parray[p].adminLevel < ADMIN_ADMIN) && (p != p1)) {
    pprintf (p,"Sorry, the journal from which you are trying to fetch is private.\n");

    if (!p1conn)
       player_remove(p1);
    return COM_OK;
    }

  if (((to[0] - 'a' - 1) > MAX_JOURNAL) && (parray[p1].adminLevel < ADMIN_ADMIN) && (!titled_player(p,parray[p1].login))) {
    pprintf (p,"%s's maximum journal entry is %c\n",parray[p1].name,toupper((char)(MAX_JOURNAL + 'A' - 1)));
    if (!p1conn)
       player_remove(p1);
    return COM_OK;
  }
  if (( p == p1) && (to[0] == from [0])) {
    pprintf (p,"Source and destination entries are the same.\n");
    return COM_OK;
  }

  /* grab from a journal */

  sprintf(fname, "%s/player_data/%c/%s.%s", stats_dir, parray[p].login[0],
          parray[p].login, STATS_JOURNAL);
  jsave_journalentry(p,to[0],p1, from[0], fname);

  }
  if (!p1conn)
     player_remove(p1);
  return COM_OK;
}