/
Crimson2/alias/
Crimson2/area.tmp/
Crimson2/area.tmp/AnomalySpaceDock/
Crimson2/area.tmp/AnomalyStation/
Crimson2/area.tmp/AntHill/
Crimson2/area.tmp/ArcticTerrarium/
Crimson2/area.tmp/BuilderCity/
Crimson2/area.tmp/Dungeon/
Crimson2/area.tmp/MiningDock/
Crimson2/area.tmp/PipeSystem/
Crimson2/area.tmp/RattArea/
Crimson2/area.tmp/RobotFactory/
Crimson2/area.tmp/SilverDale/
Crimson2/area.tmp/StarshipFearless/
Crimson2/area.tmp/StationConduits/
Crimson2/area.tmp/TerrariumAlpha/
Crimson2/area.tmp/TerrariumBeta/
Crimson2/area.tmp/TestArea/
Crimson2/area.tmp/Void/
Crimson2/area/
Crimson2/area/AnomalySpaceDock/
Crimson2/area/AnomalyStation/
Crimson2/area/MiningDock/
Crimson2/area/PipeSystem/
Crimson2/area/SilverDale/
Crimson2/area/StationConduits/
Crimson2/area/Void/
Crimson2/board/
Crimson2/clone/
Crimson2/lib/
Crimson2/mole/
Crimson2/mole/mole_src/HELP/
Crimson2/player/
Crimson2/util/
Crimson2/wldedit/
Crimson2/wldedit/res/
/* Crimson2 Mud Server
 * All source written/copyright Ryan Haksi 1995 *
 * This source code is proprietary. Use in whole or in part without
 * explicity permission by the author is strictly prohibited
 *
 * Current email address(es): cryogen@infoserve.net
 * Phone number: (604) 591-5295
 *
 * C4 Script Language written/copyright Cam Lesiuk 1995
 * Email: clesiuk@engr.uvic.ca
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "crimson2.h"
#include "macro.h"
#include "log.h"
#include "mem.h"
#include "str.h"
#include "extra.h"
#include "file.h"
#include "ini.h"
#include "send.h"
#include "thing.h"
#include "index.h"
#include "edit.h"
#include "history.h"
#include "socket.h"
#include "social.h"
#include "base.h"
#include "object.h"
#include "board.h"

#define BOARD_DEFAULT_TEXT "<Message Currently being Written>\n"

BOARDLIST *boardList = NULL;

BYTE  boardReadLog;
LWORD boardListMax = 0;
LWORD boardListByte = BOARD_LIST_SIZE;
FLAG  boardListFlag;

BYTE *bFlagList[] = {
  "UNSAVED",
  ""
};


/* 
 * Sorts by sequence number's
 */

/* returns same values as strcmp */
INDEXPROC(BoardCompareProc) { /* BYTE IndexProc(INDEX *i, void *index1, void *index2) */
  if (BoardMsg(index1)->bSequence < BoardMsg(index2)->bSequence)
    return -1;
  if (BoardMsg(index1)->bSequence > BoardMsg(index2)->bSequence)
    return 1;
  return 0;
}

/* END - INTERNAL FUNCTIONS */

void BoardInit(void) {
  FILE *boardFile;
  BYTE  buf[256];
  BYTE  tmp[128];
  LWORD virtual;
  LWORD board;
  LWORD boardNum = 0;

  boardReadLog     = INILWordRead("crimson2.ini", "boardReadLog",  0);

  boardFile = fopen("board/board.tbl", "rb");
  if (!boardFile) {
    Log(LOG_BOOT, "Unable to read board.tbl file, killing server\n");
    exit(ERROR_BADFILE);
  }

  /* report on initialization */
  sprintf(buf, "Initial boardList allocation of %ld entries\n", boardListByte/sizeof(BOARDLIST));
  Log(LOG_BOOT, buf);

  /* note allways keep totally null entry at end of board list so I can
     use it as a type array */
  REALLOC("BoardInit(board.c): boardList reallocation\n", boardList, BOARDLIST, boardListMax+2, boardListByte);
  memset(&boardList[boardListMax+1], 0, sizeof(BOARDLIST)); /* init to zeros */
 
  /* okay we opened it up so read it.... */
  while (!feof(boardFile)) {
    fscanf(boardFile, " %s", tmp); /* get file name */
    if (tmp[0] == '$' || feof(boardFile))
      break; /* Dikumud file format EOF character */
    if (tmp[0] != '#' && tmp[0] != '*') { /* ignore comments */
      /* note allways keep totally null entry at end of board list so I can
         use it as a type array */
      strcpy(boardList[boardListMax].bFileName, tmp);
      virtual = -1;
      fscanf(boardFile, " %ld ", &virtual); 
      board = BoardCreate(tmp, virtual);
      fscanf(boardFile, " "); /* strip out spaces */
      boardList[board].bEditor = FileStrRead(boardFile);

      /* load the board file for this board */
      BoardRead(board);
      BoardMsgDeleteCheck(board);
      boardNum+=boardList[board].bIndex.iNum;

      if (boardReadLog) {
        sprintf(buf,
          "Read Board:[%-18s] Msg[%5ld] [%s]\n",
          boardList[board].bFileName,
          boardList[board].bIndex.iNum,
          boardList[board].bEditor->sText
        );
        Log(LOG_BOOT, buf);
      }

    } else { /* read comment */
      fgets(buf, 256, boardFile);
    }
  }
  /* all done close up shop */
  fclose(boardFile);

  sprintf(buf,
    "Board Totals: Boards[%2ld] Messages[%5ld]\n\n",
    boardListMax,
    boardNum
   );
  Log(LOG_BOOT, buf);
}

void BoardTableWrite(void) {
  FILE *boardFile;
  LWORD board;

  boardListFlag = 0; /* mark it as up to date */
  boardFile = fopen("board/board.tbl", "wb");
  if (!boardFile) {
    Log(LOG_BOOT, "Unable to write board.tbl file\n");
    PERROR("BoardTableWrite");
    return;
  }

  /* okay we opened it up so read it.... */
  for(board=0; board<boardListMax; board++) {
    fprintf(boardFile, "%-15s %5ld ", boardList[board].bFileName, boardList[board].bVirtual);
    FileStrWrite(boardFile, boardList[board].bEditor);
  }
  fprintf(boardFile, "$\n");
  
  /* all done close up shop */
  fclose(boardFile);
}

void BoardRead(BYTE board) {
  FILE     *boardFile;
  BYTE      buf[256];
  BYTE      tmp[256];
  BOARDMSG *boardMsg;

  sprintf(buf, "board/%s.brd", boardList[board].bFileName);
  boardFile = fopen(buf, "rb");
  if (!boardFile) {
    sprintf(buf, "Unable to read board/%s, skipping\n", boardList[board].bFileName);
    Log(LOG_BOOT, buf);
    boardList[board].bName     = STRCREATE(boardList[board].bFileName);
    boardList[board].bEditor   = STRCREATE("");
    boardList[board].bMax      = 30;
    boardList[board].bMinLevel = 1;
    sprintf(buf, "boardIndex/%s", boardList[board].bFileName);
    IndexInit( &boardList[board].bIndex, 32*sizeof(BOARDMSG*), buf, 0 );
    return;
  }
  boardList[board].bName     = FileStrRead(boardFile);
  boardList[board].bFlag     = FileFlagRead(boardFile, bFlagList); 
  fscanf(boardFile, " %hd %hd ", &boardList[board].bMax, &boardList[board].bMinLevel);
  sprintf(buf, "boardIndex/%s", boardList[board].bFileName);
  IndexInit( &boardList[board].bIndex, boardList[board].bMax*sizeof(BOARDMSG*), buf, 0 );

  /* okay we opened it up so read it.... */
  while (!feof(boardFile)) {
    fgets(tmp, 256, boardFile); /* get filename */
    if (tmp[0] == '$' || feof(boardFile))
      break; /* Dikumud file format EOF character */
    if (tmp[0] == '#') { /* ignore comments */
      MEMALLOC(boardMsg, BOARDMSG, BOARDMSG_ALLOC_SIZE);
      boardMsg->bAuthor = FileStrRead(boardFile);
      boardMsg->bTitle  = FileStrRead(boardFile);
      boardMsg->bText   = FileStrRead(boardFile);
      fscanf(boardFile, " %lu ", &boardMsg->bCreateTime);
      fscanf(boardFile, " %lu ", &boardMsg->bLastRead);
      fscanf(boardFile, " %hd ", &boardMsg->bReplyTo);

      boardMsg->bSequence = boardList[board].bIndex.iNum;
      if (boardMsg->bReplyTo >= boardMsg->bSequence)
        boardMsg->bReplyTo = BOARD_NOTREPLY;
      if (boardMsg->bReplyTo < 0)
        boardMsg->bReplyTo = BOARD_NOTREPLY;
      IndexInsert(&boardList[board].bIndex, boardMsg, BoardCompareProc);
    } else {
      sprintf(buf, "Illegal character found near line:\n");
      strcat(tmp, "\n");
      Log(LOG_ERROR, buf);
      Log(LOG_ERROR, tmp);
    }
  }
  /* all done close up shop */
  fclose(boardFile);
}

BYTE BoardWrite(BYTE board) {
  LWORD i;
  FILE *boardFile;
  BYTE  buf[256];

  BITCLR(boardList[board].bFlag, B_UNSAVED);
  sprintf(buf, "board/%s.brd", boardList[board].bFileName);
  boardFile = fopen(buf, "wb");
  if (!boardFile) {
    sprintf(buf, "Unable to write file board/%s.brd\n", boardList[board].bFileName);
    Log(LOG_ERROR, buf);
    return FALSE;
  }
  FileStrWrite(boardFile,  boardList[board].bName);
  FileFlagWrite(boardFile, boardList[board].bFlag, bFlagList, '\n');
  fprintf(boardFile, "%hd %hd\n", boardList[board].bMax, boardList[board].bMinLevel);
  for (i=0; i<boardList[board].bIndex.iNum; i++) {
    fprintf(boardFile, "#%hd\n", BoardMsg(boardList[board].bIndex.iThing[i])->bSequence);
    FileStrWrite(boardFile,  BoardMsg(boardList[board].bIndex.iThing[i])->bAuthor);
    FileStrWrite(boardFile,  BoardMsg(boardList[board].bIndex.iThing[i])->bTitle);
    FileStrWrite(boardFile,  BoardMsg(boardList[board].bIndex.iThing[i])->bText);
    fprintf(boardFile, "%lu %lu %hd\n",
      BoardMsg(boardList[board].bIndex.iThing[i])->bCreateTime,   
      BoardMsg(boardList[board].bIndex.iThing[i])->bLastRead,
      BoardMsg(boardList[board].bIndex.iThing[i])->bReplyTo);
  }
  fprintf(boardFile, "$\n");
  fclose(boardFile);
  return TRUE;
}

/* ouch this is gonna be a costly proc to execute - insertion sort it...*/
LWORD BoardCreate(BYTE *fileName, LWORD virtual){
  LWORD board;
  BYTE  buf[256];

  board = 0;
  while ( (board < boardListMax)
       && (virtual > boardList[board].bVirtual)
    ) board++;

  /* note allways keep totally null entry at end of board list so I can
     use it as a type array */
  REALLOC("BoardInit(board.c): boardList reallocation\n", boardList, BOARDLIST, boardListMax+2, boardListByte);
  memset(&boardList[boardListMax+1], 0, sizeof(BOARDLIST)); /* init to zeros */
  boardListMax++;

  /* can use the os memmove instead, thought it was giving me grief but it was something else */
  MemMoveHigher(&boardList[board+1], &boardList[board], sizeof(BOARDLIST)*(boardListMax-board+1));
  memset(&boardList[board], 0, sizeof(BOARDLIST)); /* init to zeros */

  strcpy(boardList[board].bFileName, fileName);
  boardList[board].bEditor = STRCREATE("");
  boardList[board].bName = STRCREATE(fileName);
  boardList[board].bVirtual = virtual;
  boardList[board].bMax = 23; /* 23 msgs by default */
  sprintf(buf, "boardIndex/%s", boardList[board].bFileName);
  IndexInit( &boardList[board].bIndex, boardList[board].bMax*sizeof(BOARDMSG*), buf, 0 );

  return board;
}

LWORD BoardOf(LWORD virtual) {
  LWORD i;

  /* since list is sorted we can actually binary search */
  for (i=0; i<boardListMax; i++) {
    if (virtual == boardList[i].bVirtual)
      return i;
    if (virtual < boardList[i].bVirtual)
      return -1;
  }
  return -1;
}

/* delete one as necessary to keep under max */
void BoardMsgDeleteCheck(BYTE board) {
  LWORD     i;
  LWORD     lru; /* least recently used strategy */
  BOARDMSG *boardMsg;

/* First a bit of editor theory to explain how cancelling a message works:
 * while a STR is being edited in the editor its given a private STR struct
 * with sNum = STR_PRIVATE, that points to the shared sText buffer. Upon
 * exiting the editor the private STR struct is free'd and a global one
 * restored/created. Therefore if the sNum is still STR_PRIVATE then it is
 * still being edited, and we shouldnt touch it!
 */

  /* Check for Cancelled messages and turf them too */
  for (i=0; i<boardList[board].bIndex.iNum; ) {
    boardMsg = BoardMsg(boardList[board].bIndex.iThing[i]);
    if (boardMsg->bText->sNum != STR_PRIVATE 
    && !strcmp(BOARD_DEFAULT_TEXT, boardMsg->bText->sText)) {
      BoardMsgDelete(board, i);
    } else {
      i++; 
    }
  }

  /* Turf excess messages */
  while ( boardList[board].bIndex.iNum > 0
       && boardList[board].bIndex.iNum > boardList[board].bMax) {

    lru = 0;
    for (i=0; i<boardList[board].bIndex.iNum; i++) {
      if (BoardMsg(boardList[board].bIndex.iThing[i])->bCreateTime < BoardMsg(boardList[board].bIndex.iThing[lru])->bCreateTime)
        lru = i;
    }

    BoardMsgDelete(board, lru);
  }
}

BOARDMSG *BoardMsgCreate(BYTE board, THING *thing, BYTE *title, WORD replyTo){
  BOARDMSG *boardMsg;
  BOARDMSG *root;
  BOARDMSG *msg;
  LWORD     i;
  LWORD     insert;

  /* Validate replyTo so it cant crash mud if board file corrupt */
  BOUNDSET(-1, replyTo, boardList[board].bIndex.iNum-1);

  MEMALLOC(boardMsg, BOARDMSG, BOARDMSG_ALLOC_SIZE);
  boardMsg->bAuthor     = StrAlloc(thing->tSDesc);
  boardMsg->bTitle      = STRCREATE(title);
  boardMsg->bText       = STRCREATE(BOARD_DEFAULT_TEXT);
  boardMsg->bCreateTime = time(0);
  boardMsg->bLastRead   = time(0);
  boardMsg->bReplyTo    = replyTo;
  boardMsg->bNotify     = TRUE;

  if (replyTo == BOARD_NOTREPLY) {
    /* thank god no insertion required */
    insert  = boardList[board].bIndex.iNum;
  } else {
    /* find where to insert the reply */
    insert = replyTo+1;
    
    while (insert < boardList[board].bIndex.iNum) {
      root = BoardMsg(boardList[board].bIndex.iThing[insert]);
      while (root->bReplyTo != BOARD_NOTREPLY && root->bReplyTo!=replyTo) {
        root = BoardMsg(boardList[board].bIndex.iThing[root->bReplyTo]);
      }
      if (root->bReplyTo==replyTo)
        insert++;
      else
        break;
    }
  }

  /* insert as required */
  if (insert < boardList[board].bIndex.iNum) {
    /* re-arrange sequence numbers to leave a gap for the new message */
    for (i=0; i<boardList[board].bIndex.iNum; i++) {
      msg = BoardMsg(boardList[board].bIndex.iThing[i]);
      if (msg->bReplyTo >= insert)
        msg->bReplyTo++;
      if (msg->bSequence >= insert)
        msg->bSequence++;
    }
  }
  
  boardMsg->bSequence  = insert;
  IndexInsert(&boardList[board].bIndex, boardMsg, BoardCompareProc);

  /*
   * Make sure you set this when editing is complete 
   * BITSET(boardList[board].bFlag, B_UNSAVED);
   */
  return boardMsg;
}

void BoardMsgDelete(LWORD board, LWORD msgNum){
  LWORD i;
  BOARDMSG *boardMsg;
  
  /* fix up reply to's and sequence numbers */
  for (i=0; i<boardList[board].bIndex.iNum; i++) {
    boardMsg = BoardMsg(boardList[board].bIndex.iThing[i]);
    if (boardMsg->bReplyTo == msgNum) {
      boardMsg->bReplyTo = BoardMsg(boardList[board].bIndex.iThing[msgNum])->bReplyTo;
    } else if (boardMsg->bReplyTo > msgNum) {
      boardMsg->bReplyTo--;
    }
    if (boardMsg->bSequence > msgNum)
      boardMsg->bSequence--;
  } 
  IndexDelete(&boardList[board].bIndex, boardList[board].bIndex.iThing[msgNum], BoardCompareProc);
  BITSET(boardList[board].bFlag, B_UNSAVED);
}

BYTE *BoardMsgGetHeader(BYTE *buf, LWORD board, LWORD msgNum) {
  LWORD      reply;
  BOARDMSG  *boardMsg;
  struct tm *theTm;

  boardMsg = BoardMsg(boardList[board].bIndex.iThing[msgNum]);

  theTm = localtime(&boardMsg->bCreateTime);
  sprintf(buf, "^c%02d/%02d ", theTm->tm_mon+1, theTm->tm_mday);

  reply = boardMsg->bReplyTo;
  while (reply != BOARD_NOTREPLY) {
    strcat(buf, "  ");
    reply = BoardMsg(boardList[board].bIndex.iThing[reply])->bReplyTo;
  }

  sprintf(buf+strlen(buf),
    "^g%s ^Y(^y%s^Y)\n", 
    boardMsg->bTitle->sText,
    boardMsg->bAuthor->sText);

  return buf;
}

void BoardNotify(LWORD board) {
  LWORD i;
  LWORD beep;

  beep = FALSE;
  for (i=0; i<boardList[board].bIndex.iNum; i++) {
    if ( BoardMsg(boardList[board].bIndex.iThing[i])->bText->sNum != STR_PRIVATE
      && BoardMsg(boardList[board].bIndex.iThing[i])->bNotify) {
      BoardMsg(boardList[board].bIndex.iThing[i])->bNotify = FALSE;
      SendChannel("^:BRD: [", NULL, SP_CHANBOARD);
      SendChannel(boardList[board].bName->sText, NULL, SP_CHANBOARD);
      SendChannel("] ", NULL, SP_CHANBOARD);
      SendChannel(BoardMsg(boardList[board].bIndex.iThing[i])->bAuthor->sText, NULL, SP_CHANBOARD);
      SendChannel(" has written a note\n", NULL, SP_CHANBOARD);

      /* search all objectIndex for board objects and emit a beep? */
      beep = TRUE;
    }
  }

  if (beep) {
    for (i=0; i<objectIndex.iNum; i++) {
      /* Check otype */
      if (Obj(objectIndex.iThing[i])->oTemplate->oType != OTYPE_BOARD) continue;
      /* check board vnum */
      if ( OBJECTGETFIELD(objectIndex.iThing[i], OF_BOARD_BVIRTUAL) != boardList[board].bVirtual) continue;
      /* Skip it if its not in a room/player */
      if (!Base(objectIndex.iThing[i])->bInside) continue;
      if (Base(objectIndex.iThing[i])->bInside->tType==TTYPE_WLD) {
        SendAction("^b$n beeps quietly\n",   
          objectIndex.iThing[i], NULL, SEND_ROOM|SEND_AUDIBLE|SEND_VISIBLE|SEND_CAPFIRST);
      }
      if (Base(objectIndex.iThing[i])->bInside->tType>=TTYPE_CHARACTER) {
        SendAction("^b$N beeps quietly\n",   
          Base(objectIndex.iThing[i])->bInside, objectIndex.iThing[i], SEND_SRC|SEND_AUDIBLE|SEND_VISIBLE|SEND_CAPFIRST);
      }
    }
  }
}

void BoardIdle(void) {
  LWORD i;

  if (boardListFlag)
    BoardTableWrite();

  for (i=0; i<boardListMax; i++) {
    if (BIT(boardList[i].bFlag, B_UNSAVED)) {
      /* Notification */
      BoardNotify(i);
      BoardMsgDeleteCheck(i);
      BoardWrite(i);
    }
  }
}

void BoardShow(BYTE board, THING *thing) {
  BYTE       buf[256];
  LWORD      i;

  /* 
   * turf any over the limit or cancelled messages
   * Actually, this should never do anything here, 
   * The call in BoardIdle should catch it and do it there
   * or at boot time it will be caught by the call in BoardRead
   */
  BoardMsgDeleteCheck(board);

  sprintf(buf, 
    "^gBoard Title:^G[^b%s^G]\n",
    boardList[board].bName->sText);
  SendThing(buf, thing);
  for (i=0; i<boardList[board].bIndex.iNum; i++) {
    /* sprintf(buf, "^r%2ld. ", i+1); */
    /* realized it would be better if we can observe seq.# screwups */
    sprintf(buf, "^r%2hd. ", BoardMsg(boardList[board].bIndex.iThing[i])->bSequence+1); 
    SendThing(buf, thing);
    BoardMsgGetHeader(buf, board, i);
    SendThing(buf, thing);
  }
  sprintf(buf, "^g%ld ^pMessages shown.\n", boardList[board].bIndex.iNum);
  SendThing(buf, thing);
}

void BoardShowMessage(BYTE board, LWORD msgNum, THING *thing) {
  BOARDMSG  *boardMsg;
  struct tm *theTm;
  BYTE       buf[256];

  /* 
   * turf any over the limit or cancelled messages
   * Actually, this should never do anything here, 
   * The call in BoardIdle should catch it and do it there
   * or at boot time it will be caught by the call in BoardRead
   */
  BoardMsgDeleteCheck(board);

  boardMsg = BoardMsg(boardList[board].bIndex.iThing[msgNum]);
  sprintf(buf, "^gBoard Title:    ^G[^c%s^G]\n", boardList[board].bName->sText); 
  SendThing(buf, thing);
  theTm = localtime(&boardMsg->bCreateTime);
  sprintf(buf, "^gCreated:        ^G[^C%02d^b/^C%02d^G]\n",
    theTm->tm_mon+1,
    theTm->tm_mday);
  SendThing(buf, thing);
  theTm = localtime(&boardMsg->bLastRead);
  sprintf(buf, "^gLast Read:      ^G[^C%02d^b/^C%02d^G]\n", 
    theTm->tm_mon+1,
    theTm->tm_mday);
  SendThing(buf, thing);
  sprintf(buf, "^gMessage Number: ^G[^C%ld^b/^C%ld^G]\n", msgNum+1, boardList[board].bIndex.iNum); 
  SendThing(buf, thing);
  sprintf(buf, "^gAuthor:         ^G[^w%s^G]\n",
    boardMsg->bAuthor->sText);
  SendThing(buf, thing);
  SendThing(   "^gMessage Title:  ^G[^c", thing);
  SendThing(boardMsg->bTitle->sText, thing);
  SendThing("^G]\n", thing);
  SendThing("^G------------------------------------------------------------------------------\n^b", thing);
  SendThing(boardMsg->bText->sText, thing);
  SendThing("\n", thing);

  boardMsg->bLastRead = time(0);
}