LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *****************************************************************************/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "h/mud.h"

typedef struct help_data HELP_DATA;
struct help_data
{
   HELP_DATA *next, *prev;
   char *keyword;
   char *text;
   time_t updated;
   char *updated_by;
   short perm;
};

HELP_DATA *first_help, *last_help;
int top_help;
char *help_greeting;
char *un_fix_help( char *text );

void free_help( HELP_DATA *pHelp )
{
   STRFREE( pHelp->text );
   STRFREE( pHelp->keyword );
   STRFREE( pHelp->updated_by );
   DISPOSE( pHelp );
   top_help--;
}

void free_helps( void )
{
   HELP_DATA *pHelp, *pHelp_next;

   for( pHelp = first_help; pHelp; pHelp = pHelp_next )
   {
      pHelp_next = pHelp->next;
      UNLINK( pHelp, first_help, last_help, next, prev );
      free_help( pHelp );
   }
}

/*
 * Adds a help page to the list if it is not a duplicate of an existing page.
 * Page is insert-sorted by keyword. -Thoric
 * (The reason for sorting is to keep do_hlist looking nice)
 */
void add_help( HELP_DATA *pHelp )
{
   HELP_DATA *tHelp;
   int match;

   for( tHelp = first_help; tHelp; tHelp = tHelp->next )
   {
      if( pHelp->perm == tHelp->perm && strcmp( pHelp->keyword, tHelp->keyword ) == 0 )
      {
         bug( "%s: duplicate: %s. Deleting.", __FUNCTION__, pHelp->keyword );
         free_help( pHelp );
         return;
      }
      else if( pHelp->keyword
      && ( ( match = strcmp( pHelp->keyword[0] == '\'' ? pHelp->keyword + 1 : pHelp->keyword,
      tHelp->keyword[0] == '\'' ? tHelp->keyword + 1 : tHelp->keyword ) ) < 0
      || ( match == 0 && pHelp->perm > tHelp->perm ) ) )
      {
         INSERT( pHelp, tHelp, first_help, next, prev );
         break;
      }
   }
   if( !tHelp )
      LINK( pHelp, first_help, last_help, next, prev );

   top_help++;
}

void fread_help( FILE *fp )
{
   const char *word;
   bool fMatch;
   HELP_DATA *pHelp;

   CREATE( pHelp, HELP_DATA, 1 );
   pHelp->keyword = NULL;
   pHelp->text = NULL;
   pHelp->updated_by = NULL;

   for( ;; )
   {
      word = feof( fp ) ? "End" : fread_word( fp );
      fMatch = false;

      switch( UPPER( word[0] ) )
      {
         case '*':
            fMatch = true;
            fread_to_eol( fp );
            break;

         case 'E':
            if( !str_cmp( word, "End" ) )
            {
               if( !pHelp->keyword || pHelp->keyword[0] == '\0' )
               {
                  free_help( pHelp );
                  return;
               }
               if( pHelp->text && pHelp->text[0] == '.' && pHelp->text[1] == ' ' )
               {
                  char *tmptext = un_fix_help( pHelp->text );
                  STRSET( pHelp->text, tmptext );
               }
               if( !str_cmp( pHelp->keyword, "greeting" ) )
                  help_greeting = pHelp->text;
               add_help( pHelp );
               return;
	    }
	    break;

         case 'K':
            KEY( "Keyword", pHelp->keyword, fread_string( fp ) );
            break;

         case 'P':
            KEY( "Perm", pHelp->perm, fread_number( fp ) );
            break;

         case 'T':
            KEY( "Text", pHelp->text, fread_string( fp ) );
            break;

         case 'U':
            KEY( "Updated", pHelp->updated, fread_time( fp ) );
            KEY( "UpdatedBy", pHelp->updated_by, fread_string( fp ) );
            break;
      }

      if( !fMatch )
      {
         bug( "%s: no match: %s", __FUNCTION__, word );
         fread_to_eol( fp );
      }
   }
   if( pHelp )
      free_help( pHelp );
}

void load_helps( void )
{
   FILE *fp;

   top_help = 0;

   if( !( fp = fopen( HELP_FILE, "r" ) ) )
   {
      bug( "%s: Can't open %s", __FUNCTION__, HELP_FILE );
      perror( HELP_FILE );
      return;
   }

   for( ;; )
   {
      char letter;
      char *word;

      letter = fread_letter( fp );
      if( letter == '*' )
      {
         fread_to_eol( fp );
         continue;
      }
      if( letter != '#' )
      {
         bug( "%s: # not found.", __FUNCTION__ );
         break;
      }
      word = fread_word( fp );
      if( !str_cmp( word, "HELP" ) )
      {
         fread_help( fp );
         continue;
      }
      else if( !str_cmp( word, "END" ) )
         break;
      else
      {
         bug( "%s: bad section (%s).", __FUNCTION__, word );
         fread_to_eol( fp );
         continue;
      }
   }
   fclose( fp );
   fp = NULL;
}

HELP_DATA *get_help( CHAR_DATA *ch, char *argument )
{
   char argall[MIL], argone[MIL], argnew[MIL];
   HELP_DATA *pHelp;
   int lev;

   if( !argument || argument[0] == '\0' )
      argument = "summary";

   if( isdigit( argument[0] ) && !is_number( argument ) )
   {
      lev = number_argument( argument, argnew );
      argument = argnew;
   }
   else
      lev = -2;

   /* Tricky argument handling so 'help a b' doesn't match a. */
   argall[0] = '\0';
   while( argument && argument[0] != '\0' )
   {
      argument = one_argument( argument, argone );
      if( argall[0] != '\0' )
         mudstrlcat( argall, " ", sizeof( argall ) );
      mudstrlcat( argall, argone, sizeof( argall ) );
   }

   for( pHelp = first_help; pHelp; pHelp = pHelp->next )
   {
      if( ch && pHelp->perm > get_trust( ch ) )
         continue;
      if( lev != -2 && pHelp->perm != lev )
         continue;
      if( is_name( argall, pHelp->keyword ) )
         return pHelp;
   }
   return NULL;
}

/* LAWS command */
CMDF( do_laws )
{
   char buf[1024];

   if( !argument || argument[0] == '\0' )
      do_help( ch, "laws" );
   else
   {
      snprintf( buf, sizeof( buf ), "law %s", argument );
      do_help( ch, buf );
   }
}

void show_help( CHAR_DATA *ch, HELP_DATA *pHelp )
{
   char *h1, *h2;

   if( !ch || !pHelp )
      return;

   h1 = color_str( AT_HELP, ch );
   h2 = color_str( AT_HELP2, ch );

   set_pager_color( AT_HELP, ch );
   if( is_immortal( ch ) )
   {
      pager_printf( ch, "%sHelp permission: %s%s%s\r\n", h1, h2, perms_flag[pHelp->perm], h1 );
      pager_printf( ch, "%sHelp Keyword:    %s%s&D%s\r\n", h1, h2, pHelp->keyword, h1 );
   }

   if( pHelp->updated )
      pager_printf( ch, "%sLast updated on %s%s&D %sby %s%s\r\n", h1, h2, distime( pHelp->updated ),
         h1, h2, pHelp->updated_by ? pHelp->updated_by : "(Unknown)" );

   if( !pHelp->text )
      return;
   pager_printf( ch, "%s.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.\r\n%s", h1, h2 );
   send_to_pager( pHelp->text, ch );
   pager_printf( ch, "%s.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.\r\n", h1 );
}

void do_help( CHAR_DATA *ch, char *argument )
{
   HELP_DATA *pHelp, *lHelp = NULL;
   char *keyword, arg[MIL], oneword[MSL], lastmatch[MSL];
   int value = -1;
   short matched = 0, checked = 0, totalmatched = 0, found = 0;
   bool uselevel = false;

   set_pager_color( AT_NOTE, ch );

   if( !argument || argument[0] == '\0' )
      argument = "summary";

   if( ( pHelp = get_help( ch, argument ) ) )
   {
      show_help( ch, pHelp );
      return;
   }

   pager_printf( ch, "No help on \'%s\' found.\r\n", argument );

   /* Get an arg incase they do a number seperate */
   one_argument( argument, arg );

   /* See if arg is a number if so update argument */
   if( is_number( arg ) )
   {
     argument = one_argument( argument, arg );
     if( argument && argument[0] != '\0' )
     {
         value = URANGE( 0, atoi( arg ), PERM_IMP );
         uselevel = true;
     }
     else /* If no more argument put arg as argument */
        argument = arg;
   }

   if( value >= 0 )
      pager_printf( ch, "Checking for suggested helps that are permission %s.\r\n", perms_flag[value] );

   send_to_pager( "Suggested Help Files:\r\n", ch );
   strncpy( lastmatch, " ", sizeof( lastmatch ) );
   for( pHelp = first_help; pHelp; pHelp = pHelp->next )
   {
      matched = 0;
      if( !pHelp || !pHelp->keyword || pHelp->keyword[0] == '\0' || pHelp->perm > get_trust( ch ) )
         continue;

      /* Check arg if its avaliable */
      if( uselevel && pHelp->perm != value )
         continue;

      keyword = pHelp->keyword;
      while( keyword && keyword[0] != '\0' )
      {
         matched = 0;   /* Set to 0 for each time we check lol */
         keyword = one_argument( keyword, oneword );

         /* Lets check only up to 10 spots */
         for( checked = 0; checked <= 10; checked++ )
         {
            if( !oneword[checked] || !argument[checked] )
               break;
            if( LOWER( oneword[checked] ) == LOWER( argument[checked] ) )
               matched++;
         }

         if( ( matched > 1 && matched > ( checked / 2 ) ) || ( matched > 0 && checked < 2 ) )
         {
            pager_printf( ch, " %-20s ", oneword );
            if( ++found == 4 )
            {
               found = 0;
               send_to_pager( "\r\n", ch );
            }
            strncpy( lastmatch, oneword, MAX_STRING_LENGTH );
            totalmatched++;
            lHelp = pHelp;
            break;
         }
      }
   }
   if( found != 0 )
      send_to_pager( "\r\n", ch );

   if( totalmatched == 0 )
      send_to_pager( "No suggested help files.\r\n", ch );
   else if( totalmatched == 1 && lHelp )
   {
      send_to_pager( "Opening only suggested helpfile.\r\n", ch );
      show_help( ch, lHelp );
   }
}

extern char *help_greeting; /* so we can edit the greeting online */

CMDF( do_hedit )
{
   HELP_DATA *pHelp;

   if( !ch->desc )
   {
      send_to_char( "You have no descriptor.\r\n", ch );
      return;
   }

   switch( ch->substate )
   {
      default:
         break;
      case SUB_HELP_EDIT:
         if( !( pHelp = ( HELP_DATA * ) ch->dest_buf ) )
         {
            bug( "%s: sub_help_edit: NULL ch->dest_buf", __FUNCTION__ );
            stop_editing( ch );
            return;
         }
         if( help_greeting == pHelp->text )
            help_greeting = NULL;
         STRFREE( pHelp->text );
         pHelp->text = copy_buffer( ch );
         if( !help_greeting )
            help_greeting = pHelp->text;
         stop_editing( ch );
         STRSET( pHelp->updated_by, ch->name );
         pHelp->updated = current_time;
         return;
   }
   if( !( pHelp = get_help( ch, argument ) ) ) /* new help */
   {
      HELP_DATA *tHelp;
      char argnew[MIL];
      int lev;
      bool new_help = true;

      if( !argument || argument[0] == '\0' )
      {
         send_to_char( "If you would like to create a new helpfile need to supply a keyword.\r\n", ch );
         return;
      }
      for( tHelp = first_help; tHelp; tHelp = tHelp->next )
      {
         if( !str_cmp( argument, tHelp->keyword ) )
         {
            pHelp = tHelp;
            new_help = false;
            break;
         }
      }
      if( new_help )
      {
         if( isdigit( argument[0] ) )
         {
            lev = number_argument( argument, argnew );
            argument = argnew;
         }
         else
            lev = get_trust( ch );
         lev = URANGE( 0, lev, PERM_IMP );
         CREATE( pHelp, HELP_DATA, 1 );
         pHelp->keyword = STRALLOC( strupper( argument ) );
         pHelp->text = NULL;
         pHelp->perm = lev;
         add_help( pHelp );
      }
   }

   ch->substate = SUB_HELP_EDIT;
   ch->dest_buf = pHelp;
   start_editing( ch, pHelp->text );
}

/* Remove . if needed */
char *un_fix_help( char *text )
{
   static char newstr[MSL];
   int i = 0, j = 0;

   if( !text || text[0] == '\0' )
      return "";

   if( text[0] != '.' || text[1] != ' ' )
      return text;
   if( text[0] == '.' )
      i++;
   for( ; text[i] != '\0'; i++ )
      newstr[j++] = text[i];
   newstr[j] = '\0';
   return newstr;
}

/*
 * Remove carriage returns from a line
 * add in a . if needed
 * preserve the normal spaces
 */
char *help_fix( char *text )
{
   static char newstr[MSL];
   int i = 0, j = 0;

   if( !text || text[0] == '\0' )
      return "";

   if( text[0] == ' ' )
      newstr[j++] = '.';
   for( ; text[i] != '\0'; i++ )
   {
      if( text[i] != '\r' )
         newstr[j++] = text[i];
   }
   newstr[j] = '\0';
   return newstr;
}

CMDF( do_hset )
{
   HELP_DATA *pHelp;
   char arg1[MIL], arg2[MIL];

   argument = one_argument( argument, arg1 );
   if( !arg1 || arg1[0] == '\0' )
   {
      send_to_char( "Usage: hset <field> [value] [help page]\r\n\r\n", ch );
      send_to_char( "Field being one of:\r\n", ch );
      send_to_char( "  perm  keyword  remove  save\r\n", ch );
      return;
   }

   if( !str_cmp( arg1, "save" ) )
   {
      FILE *fp;
      char filename[MSL];

      log_printf( "Saving %s", HELP_FILE );

      snprintf( filename, sizeof( filename ), "%s.temp", HELP_FILE );
      if( !( fp = fopen( filename, "w" ) ) )
      {
         bug( "%s: cant open %s", __FUNCTION__, filename );
         perror( filename );
         return;
      }

      for( pHelp = first_help; pHelp; pHelp = pHelp->next )
      {
         if( !pHelp->keyword )
            continue;
         fprintf( fp, "#HELP\n" );
         if( pHelp->perm )
            fprintf( fp, "Perm      %d\n", pHelp->perm );
         fprintf( fp, "Keyword   %s~\n", pHelp->keyword );
         if( pHelp->updated )
            fprintf( fp, "Updated   %ld\n", pHelp->updated );
         if( pHelp->updated_by )
            fprintf( fp, "UpdatedBy %s~\n", pHelp->updated_by );
         if( pHelp->text )
            fprintf( fp, "Text      %s~\n", help_fix( pHelp->text ) );
         fprintf( fp, "%s\n\n", "End" );
      }
      fprintf( fp, "%s\n", "#END" );
      fclose( fp );
      fp = NULL;
      if( rename( filename, HELP_FILE ) )
         bug( "%s: Couldn't rename (%s) to (%s).", __FUNCTION__, filename, HELP_FILE );
      send_to_char( "Saved.\r\n", ch );
      return;
   }

   if( str_cmp( arg1, "remove" ) )
      argument = one_argument( argument, arg2 );

   if( !argument || argument[0] == '\0' || !( pHelp = get_help( ch, argument ) ) )
   {
      send_to_char( "Can't find help on that subject.\r\n", ch );
      return;
   }
   if( !str_cmp( arg1, "remove" ) )
   {
      UNLINK( pHelp, first_help, last_help, next, prev );
      free_help( pHelp );
      send_to_char( "Removed.\r\n", ch );
      return;
   }
   if( !str_cmp( arg1, "perm" ) )
   {
      int temp;

      if( !is_number( arg2 ) )
         temp = get_flag( arg2, perms_flag, PERM_MAX );
      else
         temp = atoi( arg2 );
      if( temp < 0 || temp > get_trust( ch ) || temp >= PERM_MAX )
      {
         send_to_char( "Invalid permission.\r\n", ch );
         return;
      }
      pHelp->perm = temp;
      send_to_char( "Done.\r\n", ch );
      return;
   }
   if( !str_cmp( arg1, "keyword" ) )
   {
      if( !arg2 || arg2[0] == '\0' )
      {
         send_to_char( "You can't set a keyword to nothing.\r\n", ch );
         return;
      }
      STRSET( pHelp->keyword, strupper( arg2 ) );
      send_to_char( "Done.\r\n", ch );
      return;
   }

   do_hset( ch, "" );
}

/*
 * Show help topics in a level range - Thoric
 * Idea suggested by Gorog
 * prefix keyword indexing added by Fireblade
 */
CMDF( do_hlist )
{
   int min, max, perm = -1, cnt = 0, level, col = 0, keycount = 0;
   char *keywords, *idx = NULL, arg[MIL], keyword[MIL], *h1, *h2;
   HELP_DATA *help;
   bool hfound;

   min = 0;
   max = get_trust( ch );

   argument = one_argument( argument, arg );
   if( arg && arg[0] != '\0' )
   {
      if( !isdigit( arg[0] ) && !is_number( arg ) )
      {
         if( perm == -1 )
            perm = get_flag( arg, perms_flag, PERM_MAX );
         if( perm == -1 )
            idx = arg;
      }
      else
         perm = URANGE( min, atoi( arg ), max );
   }

   set_pager_color( AT_HELP, ch );
   h1 = color_str( AT_HELP, ch );
   h2 = color_str( AT_HELP2, ch );

   if( perm == -1 )
      pager_printf( ch, "%sHelp Topics in perm range %s%s %sto %s%s%s:\r\n\r\n",
         h1, h2, perms_flag[min], h1, h2, perms_flag[max], h1 );
   else
      pager_printf( ch, "%sHelp Topics in perm %s%s\r\n\r\n", h1, h2, perms_flag[perm] );
   for( level = min; level <= max; level++ )
   {
      hfound = false;
      col = 0;
      for( help = first_help; help; help = help->next )
      {
         if( help->perm == level && ( !idx || nifty_is_name_prefix( idx, help->keyword ) )
         && ( perm == -1 || help->perm == perm ) )
         {
            if( !hfound )
               pager_printf( ch, " %sPermission [%s%s%s]\r\n", h1, h2, perms_flag[level], h1 );
            hfound = true;
            keywords = help->keyword;
            while( keywords && keywords[0] != '\0' )
            {
               keywords = one_argument( keywords, keyword );
               if( idx && !nifty_is_name_prefix( idx, keyword ) )
                  continue;
               pager_printf( ch, "%s%20.20s&D", h2, keyword );
               if( ++col == 3 )
               {
                  send_to_pager( "\r\n", ch );
                  col = 0;
               }
               else
                  send_to_pager( " ", ch );
               keycount++;
            }
            ++cnt;
         }
      }
      if( col != 0 )
         send_to_pager( "\r\n", ch );
   }
   if( cnt )
      pager_printf( ch, "\r\n%s%d %spages found with a total of %s%d %skeywords.\r\n", h2, cnt, h1, h2, keycount, h1 );
   else
      pager_printf( ch, "%sNone found.\r\n", h1 );
}

CMDF( do_credits )
{
   do_help( ch, "credits" );
}

CMDF( do_check_helps )
{
   int col = 0, skcnt = 0, cmdcnt = 0, hash;
   int sn;
   CMDTYPE *command;

   for( hash = 0; hash < 126; hash++ )
   {
      for( command = command_hash[hash]; command; command = command->next )
      {
         if( get_help( NULL, command->name ) )
            continue;
         if( ++cmdcnt == 1 )
            send_to_pager( "\r\nCommands that have no help file:\r\n", ch );
         pager_printf( ch, "%s%15.15s", col != 0 ? "  " : "", command->name );
         if( ++col == 5 )
         {
            col = 0;
            send_to_pager( "\r\n", ch );
         }
      }
   }
   if( col != 0 )
      send_to_pager( "\r\n", ch );

   col = 0;
   for( sn = 0; sn < top_sn; sn++ )
   {
      if( !skill_table[sn] || !skill_table[sn]->name )
         continue;
      if( get_help( NULL, skill_table[sn]->name ) )
         continue;
      if( ++skcnt == 1 )
         send_to_pager( "\r\nSkills that have no help file:\r\n", ch );
      pager_printf( ch, "%s%15.15s", col != 0 ? "  " : "", skill_table[sn]->name );
      if( ++col == 5 )
      {
         col = 0;
         send_to_pager( "\r\n", ch );
      }
   }
   if( col != 0 )
      send_to_pager( "\r\n", ch );

   if( !cmdcnt )
      send_to_pager( "\r\nAll of your commands have matching help files.\r\n", ch );
   else
      pager_printf( ch, "\r\n%d command%s were found to not have a matching help file.\r\n", cmdcnt, cmdcnt == 1 ? "" : "s" );
   if( !skcnt )
      send_to_pager( "All of your skills have matching help files.\r\n", ch );
   else
      pager_printf( ch, "%d skill%s were found to not have a matching help file.\r\n", skcnt, skcnt == 1 ? "" : "s" );
}

bool valid_help( char *argument )
{
   if( !argument || !get_help( NULL, argument ) )
      return false;
   return true;
}