/
/*
  SillyMUD Distribution V1.1b             (c) 1993 SillyMUD Developement
 
  See license.doc for distribution terms.   SillyMUD is based on DIKUMUD
*/

#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include "protos.h"


#define MAX_MSGS 99	            /* Max number of messages.          */
#define MAX_MESSAGE_LENGTH 2048     /* that should be enough            */
#define NUM_BOARDS 3

struct message {
  char *date;
  char *title;
  char *author;
  char *text;
};

struct board {
  struct message msg[MAX_MSGS+1];
  int number;
};

static struct board_lock_struct {
  struct char_data *locked_for;
  bool lock;
} board_lock[NUM_BOARDS];


int min_read_level[] = { 0, 51, 1};
int min_write_level[] = { 1, 51, 1};
int min_remove_level[] = { 51, 51, 51};

struct board boards[NUM_BOARDS];
struct board *curr_board;
struct message *curr_msg;  
extern struct char_data *character_list;

/* This sets the minimum level needed to read/write/look at these boards
   mainly included to enable the creation of a "wizard-only" board        */

char save_file[NUM_BOARDS][20] = { 
  "mortal.board" , "wiz.board", "skexie.board" };

    
/* These are the binary files in which to save/load messages */

void board_write_msg(struct char_data *ch, char *arg, int bnum);
int board_display_msg(struct char_data *ch, char *arg, int bnum);
int board_remove_msg(struct char_data *ch, char *arg, int bnum);
void board_save_board();
void board_load_board();
int board_show_board(struct char_data *ch, char *arg, int bnum);
int fwrite_string(char *buf, FILE *fl);

/* board.c version 1.2 - Jun 1991 by Twilight.

1.2 changes:

c   Added a board and message structure
   took out all pointers in an effort to insure integrity in memory.
   Added differentiation between minimum read levels and minimum write/remove
   levels.

1.1 changes:

   Major repairs-- now allows multiple boards define at compile-time.  Set the
   constants NUM_BOARDS and add the new V-Numbers to the if/then structure directly
   below.  Also you must attach the board.c procedure in spec_assign.c as usual.

   Log message removals and restrict them to level 15 and above.
   Fixed act message resulting from message removal 
   Removed unused procedure "fix_long_desc"
   Added a message to inform others in room of a read in progress
   Added minimum level check for each board 
   (defined in array min_board_level[NUM_BOARDS]

*/

int board(struct char_data *ch, int cmd, char *arg, struct obj_data *obj, int type)
{
  static int has_loaded = 0;
  char buf[80];
  int bnum = -1;
  int obj_num;

  if (type != PULSE_COMMAND)
    return(FALSE);

  if (!ch->desc)
    return(0); /* By MS or all NPC's will be trapped at the board */
  
  if (!has_loaded)
    {
      board_load_board();
      has_loaded = 1;
    }

  if (!cmd)
    return(FALSE);

  /* Identify which board we're dealing with */
  
  obj_num = (obj->item_number);
  if (obj_num == (real_object(3099)))  bnum = 0;
  else if (obj_num == (real_object(3098)))  bnum = 1;
  else if (obj_num == (real_object(3097))) bnum = 2;

  switch (cmd) {
  case 15:  /* look */
    return(board_show_board(ch, arg, bnum));
  case 149: /* write */
    board_write_msg(ch, arg, bnum);
    return 1;
  case 63: /* read */
    return(board_display_msg(ch, arg, bnum));
  case 66: /* remove */
    return(board_remove_msg(ch, arg, bnum));
  default:
    return 0;
  }
}


void board_write_msg(struct char_data *ch, char *arg, int bnum) {

  int highmessage;
  char buf[MAX_STRING_LENGTH];
  long ct; /* clock time */
  char *tmstr;

  extern struct time_info_data time_info;
  extern char *month_name[];

  if ( bnum == -1 ) {
    log("Board special procedure called for non-board object.\r\n");
    send_to_char("This board is not in operation at this time.\n\r", ch);
    return;
  }

  curr_board = &boards[bnum];

  if (GetMaxLevel(ch) < min_write_level[bnum]) {
    send_to_char("You pick up a quill to write, but realize you're not powerful enough\n\r",ch);
    send_to_char("to submit intelligent material to THIS board.\n\r",ch);
    return;
  }

  if ( (curr_board->number) > (MAX_MSGS - 1) ) {
    send_to_char("The board is full already.\n\r", ch);
    return;
  }

  /* Check for locks, return if lock is found on this board */

  if (board_check_locks(bnum, ch))
    return;

  /* skip blanks */

  for(; isspace(*arg); arg++);

  if (!*arg) {
    send_to_char("The board has now been saved permanently to disk.\n\rTo write a new message, use WRITE followed by a title.\n\r", ch);
    return;
  }

  /* Now we're committed to writing a message.  Let's lock the board. */

  board_lock[bnum].lock = 1;
  board_lock[bnum].locked_for = ch;

  /* Lock set */

  highmessage = boards[bnum].number;
  curr_msg = &curr_board->msg[++highmessage];

  if (!(strcmp("Topic",arg))) {
    curr_msg = &curr_board->msg[0];
    free(curr_msg->title);
    free(curr_msg->text);
    free(curr_msg->author);
    free(curr_msg->date);
    (boards[bnum].number)--;
  }
  curr_msg->title = (char *)malloc(strlen(arg)+1);
  strcpy(curr_msg->title, arg);
  curr_msg->author = (char *)malloc(strlen(GET_NAME(ch))+1);
  strcpy(curr_msg->author, GET_NAME(ch));
  ct = time(0);
  tmstr = (char *)asctime(localtime(&ct));
  *(tmstr + strlen(tmstr) - 1) = '\0';
  sprintf(buf,"%.10s",tmstr);
  curr_msg->date = (char *)malloc(strlen(buf)+1);
  strcpy(curr_msg->date, buf);
  send_to_char("Write your message. Terminate with a @.\n\r\n\r", ch);
  act("$n starts to write a message.", TRUE, ch, 0, 0, TO_ROOM);

  /* Take care of free-ing and zeroing if the message text is already
     allocated previously */

  if (curr_msg->text)
    free (curr_msg->text);
  curr_msg->text = 0;

  /* Initiate the string_add procedures from comm.c */

  ch->desc->str = &curr_msg->text;
  ch->desc->max_str = MAX_MESSAGE_LENGTH;
  (boards[bnum].number)++;
  if (boards[bnum].number < 0)
    boards[bnum].number = 0;
}

int board_remove_msg(struct char_data *ch, char *arg, int bnum) {

  /* This should now be fixed so that low level chars can remove armor and such. */

  int ind, tmessage;
  char buf[256], number[MAX_INPUT_LENGTH];
  
  one_argument(arg, number);
  
  if (!*number || !isdigit(*number))
    return(0);
  
  if (!(tmessage = atoi(number))) return(0);
  
  if ( bnum == -1 ) {
    log("Board special procedure called for non-board object.\r\n");
    send_to_char("This board is not in operation at this time.\n\r", ch);
    return 1;
  }

  curr_board = &boards[bnum];

  if (GetMaxLevel(ch) < min_remove_level[bnum]) {
    send_to_char("You try and grab one of the notes of the board but get a nasty\n\r",ch);
    send_to_char("shock.  Maybe you'd better leave it alone.\n\r",ch);
    return 1;
  }

  if (curr_board->number < 1) {
    send_to_char("The board is empty!\n\r", ch);
    return(1);
  }

  if (tmessage < 0 || tmessage > curr_board->number) {
    send_to_char("That message exists only in your imagination..\n\r",
		 ch);
    return(1);
  }

  /* Check for board locks, return if lock is found */
  
  if (board_check_locks(bnum, ch))
    return(1);

  ind = tmessage;

  free(curr_board->msg[ind].text);
  free(curr_board->msg[ind].date);
  free(curr_board->msg[ind].author);
  free(curr_board->msg[ind].title);

  for ( ; ind < (curr_board->number) ; ind++ )
    curr_board->msg[ind] = curr_board->msg[ind+1];

/* You MUST do this, or the next message written after a remove will */
/* end up doing a free(curr_board->msg[ind].text) because it's not!! */
/* Causing strange shit to happen, because now the message has a     */
/* To a memory location that doesn't exist, and if THAT message gets */
/* Removed, it will destroy what it's pointing to. THIS is the board */
/* Bug we've been looking for!        -=>White Gold<=-               */

  curr_board->msg[curr_board->number].text = NULL;
  curr_board->msg[curr_board->number].date = NULL;
  curr_board->msg[curr_board->number].author = NULL;
  curr_board->msg[curr_board->number].title = NULL;

  curr_board->number--;

  send_to_char("Message removed.\n\r", ch);
  sprintf(buf, "%s just removed message %d.", ch->player.name, tmessage);

  /* Removal message also repaired */

  act(buf, FALSE, ch, 0, 0, TO_ROOM);
  sprintf((buf+strlen(buf)-1)," from board %d.",bnum);
  log(buf);  /* Message removals now logged. */

  board_save_board(bnum);
  return(1);
}

char *fix_returns(char *text_string)
{
  char *localbuf;
  int point=0;
  int point2 = 0;

  if (!text_string) {
    CREATE(localbuf,char,2);
    strcpy(localbuf,"\n");
    return(localbuf);
  }

  if (!(*text_string)) {
    CREATE(localbuf,char,strlen("(NULL)")+1);
    strcpy(localbuf,"(NULL)");
    return(localbuf);
  }

  CREATE(localbuf,char,strlen(text_string));

  while(*(text_string+point) != '\0') 
    if (*(text_string+point) != '\r') {
      *(localbuf+point2) = *(text_string+point);
      point2++;
      point++;
    }
    else
      point++;
  *(localbuf + point2) = '\0'; /* You never made sure of null termination */
  return(localbuf);
}
  
void board_save_board(bnum) {

  FILE *the_file;
  int ind;
  char buf[256];
  char *temp_add;

  /* We're assuming the board number is valid since it was passed by
     out own code */

  curr_board = &boards[bnum];

  the_file = fopen(save_file[bnum], "w");

  if (!the_file) {
      log("Unable to open/create savefile for bulletin board..\n\r");
      return;
    }

  fprintf(the_file," %d ", curr_board->number);
  for (ind = 0; ind <= curr_board->number; ind++) {
    curr_msg = &curr_board->msg[ind];
    fwrite_string(curr_msg->title, the_file);
    fwrite_string(curr_msg->author, the_file);
    fwrite_string(curr_msg->date, the_file);
    fwrite_string((temp_add = fix_returns(curr_msg->text)), the_file);
    free(temp_add);
  }
  fclose(the_file);
  return;
}

void board_load_board() {

  FILE *the_file;
  int ind;
  int bnum;
  char buf[256];
  
  memset(boards, 0, sizeof(boards)); /* Zero out the array, make sure no */
                                     /* Funky pointers are left in the   */
                                     /* Allocated space                  */

  for ( bnum = 0 ; bnum < NUM_BOARDS ; bnum++ ) {
    board_lock[bnum].lock = 0;
    board_lock[bnum].locked_for = 0;
  }

  for (bnum = 0; bnum < NUM_BOARDS; bnum++) {
    boards[bnum].number = -1;
    the_file = fopen(save_file[bnum], "r");
    if (!the_file) {
      sprintf(buf,"Can't open message file for board %d.\n\r",bnum);
      log(buf,0);
      continue;
    }

    fscanf( the_file, " %d ", &boards[bnum].number);
    if (boards[bnum].number < 0 || boards[bnum].number > MAX_MSGS || 
	feof(the_file)) {
      log("Board-message file corrupt, nonexistent, or empty.\n\r");
      boards[bnum].number = -1;
      fclose(the_file);
      continue;
    }

    curr_board = &boards[bnum];

    for (ind = 0; ind <= curr_board->number; ind++) {
      curr_msg = &curr_board->msg[ind];
      curr_msg->title = (char *)fread_string (the_file);
      curr_msg->author = (char *)fread_string (the_file);
      curr_msg->date = (char *)fread_string (the_file);
      curr_msg->text = (char *)fread_string (the_file);
    }
    fclose(the_file);
  }
}

int board_display_msg(struct char_data *ch, char *arg, int bnum)
{
  char buf[512], number[MAX_INPUT_LENGTH], buffer[MAX_STRING_LENGTH];
  int tmessage;

  one_argument(arg, number);

  if (!*number || !isdigit(*number))
    return(0);

  if (!(tmessage = atoi(number))) return(0);

  curr_board = &boards[bnum];

  if ((boards[bnum].number != -1) &&
      (tmessage >= 0 && tmessage <= curr_board->number) &&
      (GetMaxLevel(ch) < min_read_level[bnum]) &&
      (strcmp(GET_NAME(ch), curr_board->msg[tmessage].author))) ;
  else 
  if ( GetMaxLevel(ch) < min_read_level[bnum] ) {
    send_to_char("You try and look at the messages on the board but you\n\r",
                 ch);
    send_to_char("cannot comprehend their meaning.\n\r\n\r",ch);
    act("$n tried to read the board, but looks bewildered.",TRUE,ch, 0, 0,
        TO_ROOM);
    return(1);
  }

  if (boards[bnum].number == -1) {
    send_to_char("The board is empty!\n\r", ch);
    return(1);
  }
  
  if (tmessage < 0 || tmessage > curr_board->number) {
    send_to_char("That message exists only in your imagination..\n\r",ch);
    return(1);
  }

  curr_msg = &curr_board->msg[tmessage];

  sprintf(buffer, "Message %2d (%s): %-15s -- %s", tmessage, curr_msg->date, curr_msg->author, curr_msg->title );
  sprintf(buffer + strlen(buffer), "\n\r----------\n\r%s", (curr_msg->text?curr_msg->text:"(null)"));
  page_string(ch->desc, buffer, 1);
  return(1);

/*
  sprintf(buf, "$n reads message %d titled : %s.",tmessage, curr_msg->title);
  act(buf, TRUE, ch, 0, 0, TO_ROOM);
*/
}
		
int board_show_board(struct char_data *ch, char *arg, int bnum)
{
  int i;
  char buf[MAX_STRING_LENGTH+50], tmp[MAX_INPUT_LENGTH];
                          /* ^ had a few bus errors, *shrug* */
  one_argument(arg, tmp);

  if (!*tmp || !isname(tmp, "board bulletin"))
    return(0);

  if ((GetMaxLevel(ch) < min_read_level[bnum]) && (bnum !=5)) 
    /* Skip if board 5 (Reimb board) */
{ 
    send_to_char("You try and look at the messages on the board but you\n\r",ch);
    send_to_char("cannot comprehend their meaning.\n\r",ch);
    act("$n tried to read the board, but looks bewildered.",TRUE,ch, 0, 0, TO_ROOM);
    return(1);
  }

  curr_board = &boards[bnum];

  act("$n studies the board.", TRUE, ch, 0, 0, TO_ROOM);

  strcpy(buf,"This is a bulletin board. Usage: READ/REMOVE <messg #>, WRITE <header>\n\r");
  if (boards[bnum].number == -1)
    strcat(buf, "The board is empty.\n\r");
  else {
    sprintf(buf + strlen(buf), "There are %d messages on the board.\n\r",
	    curr_board->number);
    sprintf(buf + strlen(buf), "\n\rBoard Topic:\n\r%s------------\n\r",curr_board->msg[0].text);
    for ( i = 1 ; i <= curr_board->number ; i++ ) 
/*      if (((GET_MAX_LEVEL(ch) < min_read_level[bnum]) &&
           (strcmp(ch->name, curr_board->msg[i].author))) ||
          (GET_MAX_LEVEL(ch) >= min_read_level[bnum]))  */
       sprintf(buf + strlen(buf), "%-2d : %-15s (%s) -- %s\n\r", i , 
               curr_board->msg[i].author, curr_board->msg[i].date,
               curr_board->msg[i].title);
  }
  page_string(ch->desc, buf, 1);
  return(1);
}

int fwrite_string (char *buf, FILE *fl)
{
  return (fprintf(fl, "%s~\n", buf));
}

int board_check_locks (int bnum, struct char_data *ch) {
  
  char buf[MAX_INPUT_LENGTH];
  struct char_data *tmp_char;
  bool found = FALSE;
  if (!board_lock[bnum].lock) return(0);
  
  /* FIRST lets' see if this character is even in the game anymore! -WG-*/
  for (tmp_char = character_list; tmp_char; tmp_char = tmp_char->next)
    {
      if (tmp_char == board_lock[bnum].locked_for)
        {
          found = TRUE;
          break;
        }
    }
  if (!found)
    {
      log("Board: board locked for a user not in game.");
      board_lock[bnum].lock = 0;
      board_lock[bnum].locked_for = NULL;
      return(0);
    }

  /* Check for link-death of lock holder */

  if (!board_lock[bnum].locked_for->desc) {
    sprintf(buf,"You push %s aside and approach the board.\n\r",board_lock[bnum].locked_for->player.name);
    send_to_char(buf, ch);
  }

  /* Else see if lock holder is still in write-string mode */

  else if (board_lock[bnum].locked_for->desc->str) { /* Lock still holding */
    sprintf(buf,"You try to approach the board but %s blocks your way.\n\r",board_lock[bnum].locked_for->player.name);
    send_to_char(buf, ch);
    return (1);
  }

  /* Otherwise, the lock has been lifted */

  board_save_board(bnum);
  board_lock[bnum].lock = 0;
  board_lock[bnum].locked_for = 0;
  return(0);
}