/* GNUPLOT - help.c
 *
 * Copyright (C) 1986, 1987, 1990, 1991   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the modified code.  Modifications are to be distributed
 * as patches to released version.
 * 
 * This software is provided "as is" without express or implied warranty.
 *
 *
 * AUTHORS
 *
 *   Original Software:
 *     Thomas Williams,  Colin Kelley.
 *
 *   Gnuplot 2.0 additions:
 *       Russell Lang, Dave Kotz, John Campbell.
 *
 *   Gnuplot 3.0 additions:
 *       Gershon Elber and many others.
 *
 * Send your comments or suggestions to
 *  pixar!info-gnuplot@sun.com.
 * This is a mailing list; to join it send a note to
 *  pixar!info-gnuplot-request@sun.com. 
 * Send bug reports to
 *  pixar!bug-gnuplot@sun.com.
 */

#include <stdio.h>
#include <string.h>
#ifndef NOSTDLIB_H
#include <stdlib.h>
#endif
#ifndef NOMALLOC_H
#include <malloc.h>
#endif


extern int errno;
extern char *getenv(const char *);
extern FILE *fopen(const char *, const char *);
static int instring(char *str, char c);

#define    SAME    0    /* for strcmp() */

#include "help.h"    /* values passed back */

/* help -- help subsystem that understands defined keywords
**
** Looks for the desired keyword in the help file at runtime, so you
** can give extra help or supply local customizations by merely editing
** the help file.
**
** The original (single-file) idea and algorithm is by John D. Johnson,
** Hewlett-Packard Company.  Thanx and a tip of the Hatlo hat!
**
** Much extension by David Kotz for use in gnutex, and then in gnuplot.
** Added output paging support, both unix and builtin. Rewrote completely
** to read helpfile into memory, avoiding reread of help file. 12/89.
**
** Modified by Russell Lang to avoid reading completely into memory
** if MSDOS defined.  This uses much less memory.  6/91
**
** The help file looks like this (the question marks are really in column 1):
**
**     ?topic
**     This line is printed when the user wants help on "topic".
**     ?keyword
**     ?Keyword
**     ?KEYWORD
**     These lines will be printed on the screen if the user wanted
**     help on "keyword", "Keyword", or "KEYWORD".  No casefolding is
**    done on the keywords.
**     ?subject
**     ?alias
**     This line is printed for help on "subject" and "alias".
**     ? or double? ( avoid trigraph)
**    
**     Since there is a null keyword for this line, this section
**     is printed when the user wants general help (when a help
**     keyword isn't given).  A command summary is usually here.
**    Notice that the null keyword is equivalent to a "?" keyword
**    here, because of the '?' and 'double ?' topic lines above.
**   If multiple keywords are given, the first is considered the
**   'primary' keyword. This affects a listing of available topics.
**     ?last-subject
**     Note that help sections are terminated by the start of the next
**     '?' entry or by EOF.  So you can't have a leading '?' on a line
**     of any help section.  You can re-define the magic character to
**    recognize in column 1, though, if '?' is too useful.  (Try ^A.)
*/

#define    KEYFLAG    '?'    /* leading char in help file topic lines */

/*
** Calling sequence:
**    int result;        # 0 == success
**    char *keyword;        # topic to give help on
**    char *pathname;        # path of help file
**      int subtopics;        # set to TRUE if only subtopics to be listed
**                # returns TRUE if subtopics were found
**    result = help(keyword, pathname, &subtopics);
** Sample:
**    cmd = "search\n";
**    helpfile = "/usr/local/lib/program/program.help";
**    subtopics = FALSE;
**    if (help(cmd, helpfile, &subtopics) != H_FOUND)
**        printf("Sorry, no help for %s", cmd);
**
**
** Speed this up by replacing the stdio calls with open/close/read/write.
*/
#ifdef    WDLEN
#  define    PATHSIZE    WDLEN
#else
#  define    PATHSIZE    BUFSIZ
#endif

typedef int boolean;
#ifndef TRUE
#define TRUE (1)
#define FALSE (0)
#endif

typedef struct line_s LINEBUF;
struct line_s {
    char *line;            /* the text of this line */
    LINEBUF *next;            /* the next line */
};

typedef struct linkey_s LINKEY;
struct linkey_s {
    char *key;                /* the name of this key */
    long pos;                /* ftell position */
    LINEBUF *text;            /* the text for this key */
    boolean primary;        /* TRUE -> is a primary name for a text block */
    LINKEY *next;            /* the next key in linked list */
};

typedef struct key_s KEY;
struct key_s {
    char *key;                /* the name of this key */
    long pos;                /* ftell position */
    LINEBUF *text;            /* the text for this key */
    boolean primary;        /* TRUE -> is a primary name for a text block */
};
static LINKEY *keylist = NULL;    /* linked list of keys */
static KEY *keys = NULL;        /* array of keys */
static int keycount = 0;        /* number of keys */
static FILE *helpfp = NULL;

static int LoadHelp(char *path);
static void sortkeys(void);
static int keycomp(const void *a, const void *b);
static LINEBUF *storeline(char *text);
static LINKEY *storekey(char *key);
static KEY *FindHelp(char *keyword);
static boolean Ambiguous(KEY *key, int len);

/* Help output */
static void PrintHelp(KEY *key, boolean *subtopics);
static void ShowSubtopics(KEY *key, boolean *subtopics);
static void StartOutput(void);
static void OutLine(char *line);
static void EndOutput(void);
static FILE *help_stream;        /* for unix pager, if any */
static int pagelines;        /* count for builtin pager */
#define SCREENSIZE 24        /* lines on screen (most have at least 24) */

/* help:
 * print a help message
 * also print available subtopics, if subtopics is TRUE
 */


/*
#if !defined(ultrix) && !defined(__STDC__)
ULTRIX compiler crashes on this line, and it is well it should...
extern void qsort (void *, int, int, int (*) (KEY *, KEY *));
#endif
*/

static int help(char *keyword, char *path, boolean *subtopics)
                      /* on this topic */
                       /* from this file */
                       /* (in) - subtopics only? */
                        /* (out) - are there subtopics? */
{
    static char oldpath[PATHSIZE] = "";    /* previous help file */
    int status;            /* result of LoadHelp */
    KEY *key;            /* key that matches keyword */

    /*
    ** Load the help file if necessary (say, first time we enter this routine,
    ** or if the help file changes from the last time we were called).
    ** Also may occur if in-memory copy was freed.
    ** Calling routine may access errno to determine cause of H_ERROR.
    */
    errno = 0;
    if (strncmp(oldpath, path, PATHSIZE) != SAME)
     Ft_FreeHelp();
    if (keys == NULL) {
       status = LoadHelp(path);
       if (status == H_ERROR)
        return(status);

       /* save the new path in oldpath */
       if (strlen(path) < PATHSIZE)
        (void) strcpy(oldpath, path);
       else {                /* not enough room in oldpath, sigh */
          (void) strncpy(oldpath, path, PATHSIZE - 1);
          oldpath[PATHSIZE - 1] = '\0';
       }
    }

    /* look for the keyword in the help file */
    key = FindHelp(keyword);
    if (key != NULL) {
       /* found the keyword: print help and return */
       PrintHelp(key, subtopics);
       status = H_FOUND;
    } else {
       status = H_NOTFOUND;
    }

    return(status);
}

/* we only read the file once, into memory
 * except for MSDOS when we don't read all the file -
 * just the keys and location of the text
 */
static int
LoadHelp(char *path)
{
    LINKEY *key=NULL;        /* this key */
    long pos=0;              /* ftell location within help file */
    char buf[BUFSIZ];        /* line from help file */
    LINEBUF *head;           /* head of text list  */
    LINEBUF *firsthead = NULL;
    boolean primary;        /* first ? line of a set is primary */
    boolean flag;

    if ((helpfp = fopen(path, "r")) == NULL) {
       /* can't open help file, so error exit */
       return (H_ERROR);
    }

    /*
    ** The help file is open.  Look in there for the keyword.
    */
    (void) fgets(buf, BUFSIZ - 1, helpfp);
    while (!feof(helpfp)) {
       /*
        ** Make an entry for each synonym keyword
        */
       primary = TRUE;
       while (buf[0] == KEYFLAG) {
          key = storekey(buf+1);    /* store this key */
          key->primary = primary;
          key->text = NULL;            /* fill in with real value later */
          key->pos = 0;                /* fill in with real value later */
          primary = FALSE;
          pos = ftell(helpfp);
          if (fgets(buf, BUFSIZ - 1, helpfp) == (char *)NULL)
            break;
       }
       /*
        ** Now store the text for this entry.
        ** buf already contains the first line of text.
        */
#ifndef MSDOS
       firsthead = storeline(buf);
       head = firsthead;
#endif
       while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
        && (buf[0] != KEYFLAG) ){
#ifndef MSDOS
          /* save text line */
          head->next = storeline(buf);
          head = head->next;
#endif
       }
       /* make each synonym key point to the same text */
       do {
          key->pos = pos;
          key->text = firsthead;
          flag = key->primary;
          key = key->next;
       } while ( flag!=TRUE  &&  key!=NULL );
    }
#ifndef MSDOS
    (void) fclose(helpfp);
#endif

    /* we sort the keys so we can use binary search later */
    sortkeys();
    return(H_FOUND); /* ok */
}

/* make a new line buffer and save this string there */
static LINEBUF *
storeline(char *text)
{
    LINEBUF *new;

    new = (LINEBUF *)malloc(sizeof(LINEBUF));
    if (new == NULL) {
        fprintf(stderr, "help: not enough memory to store help file.\n");
        return(new);
    }
    if (text != NULL) {
        new->line = (char *) malloc((unsigned int)(strlen(text)+1));
        if (new->line == NULL) {
            fprintf(stderr, "help: not enough memory to store help file.\n");
            free(new);
            return(NULL);
        }
        (void) strcpy(new->line, text);
    } else {
        new->line = NULL;
    }
    new->next = NULL;

    return(new);
}

/* Add this keyword to the keys list, with the given text */
static LINKEY *
storekey(char *key)
{
    LINKEY *new;

    key[strlen(key)-1] = '\0'; /* cut off \n  */

    new = (LINKEY *)malloc(sizeof(LINKEY));
    if (new == NULL) {
        fprintf(stderr, "help: not enough memory to store help file.\n");
        return(NULL);
    }
    new->key = (char *) malloc((unsigned int)(strlen(key)+1));
    if (new->key == NULL) {
        fprintf(stderr, "help: not enough memory to store help file.\n");
        free(new);
        return(NULL);
    }
    (void) strcpy(new->key, key);

    /* add to front of list */
    new->next = keylist;
    keylist = new;
    keycount++;
    return(new);
}

/* we sort the keys so we can use binary search later */
/* We have a linked list of keys and the number.
 * to sort them we need an array, so we reform them into an array,
 * and then throw away the list.
 */
static void
sortkeys(void)
{
    LINKEY *p,*n;            /* pointers to linked list */
    int i;                /* index into key array */
   
    /* allocate the array */
    keys = (KEY *)malloc((unsigned int)((keycount+1) * sizeof(KEY)));
    if (keys == NULL) {
        fprintf(stderr, "help: not enough memory to store help file.\n");
        return;
    }
    /* copy info from list to array, freeing list */
    for (p = keylist, i = 0; p != NULL; p = n, i++) {
       keys[i].key = p->key;
       keys[i].pos = p->pos;
       keys[i].text = p->text;
       keys[i].primary = p->primary;
       n = p->next;
       free( (char *)p );
    }

    /* a null entry to terminate subtopic searches */
    keys[keycount].key = NULL;
    keys[keycount].pos = 0;
    keys[keycount].text = NULL;

    /* sort the array */
    /* note that it only moves objects of size (two pointers + long + int) */
    /* it moves no strings */
    qsort((char *)keys, keycount, sizeof(KEY), keycomp);
    return;
}

static int
keycomp(void const *a, void const *b)
{
    return (strcmp(((KEY *)a)->key, ((KEY*)b)->key));
}

/* Free the help file from memory. */
/* May be called externally if space is needed */
void
Ft_FreeHelp(void)
{
    int i;                /* index into keys[] */
    LINEBUF *t, *next;

    if (keys == NULL)
     return;

    for (i = 0; i < keycount; i++) {
       free( (char *)keys[i].key );
       if (keys[i].primary)   /* only try to release text once! */
       for (t = keys[i].text; t != NULL; t = next) {
          free( (char *)t->line );
          next = t->next;
          free( (char *)t );
       }
    }
    free( (char *)keys );
    keys = NULL;
    keycount = 0;
#ifdef MSDOS
    (void) fclose(helpfp);
#endif
}

/* FindHelp:
 *  Find the key that matches the keyword.
 *  The keys[] array is sorted by key.
 *  We could use a binary search, but a linear search will aid our
 *  attempt to allow abbreviations. We search for the first thing that
 *  matches all the text we're given. If not an exact match, then
 *  it is an abbreviated match, and there must be no other abbreviated
 *  matches -- for if there are, the abbreviation is ambiguous.
 *  We print the ambiguous matches in that case, and return not found.
 */
static KEY *                /* NULL if not found */
FindHelp(char *keyword)
                              /* string we look for */
{
    KEY *key;
    int len = strlen(keyword);
    int compare;

    /**** print the whole linked list
    for (key = keys; key->key != NULL ; key++)
        fprintf(stderr, "%s\n", key->key);
    ****/
	if (!len)
		return(keys);
    for (key = keys, compare = 1; key->key != NULL && compare > 0; key++) {
       compare = strncmp(keyword, key->key, len);
       if (compare == 0)    /* we have a match! */
        if (!Ambiguous(key, len)) {
            /* non-ambiguous abbreviation */
            (void) strcpy(keyword, key->key); /* give back the full spelling */
            return(key);        /* found!! */
        }
    }

    /* not found, or ambiguous */
    return(NULL);
}

/* Ambiguous:
 * Check the key for ambiguity up to the given length.
 * It is ambiguous if it is not a complete string and there are other
 * keys following it with the same leading substring.
 */
static boolean
Ambiguous(KEY *key, int len)
{
    char *first;
    char *prev;
    boolean status = FALSE;    /* assume not ambiguous */
    int compare;
    int sublen;

    if (key->key[len] == '\0')
     return(FALSE);
   
    for (prev = first = key->key, compare = 0, key++;
        key->key != NULL && compare == 0; key++) {
       compare = strncmp(first, key->key, len);
       /***
       fprintf(stderr, "Compare %s with %s: %d\n", first, key->key, !compare);
       ***/
       if (compare == 0) {
          /* So this key matches the first one, up to len.
           * But is it different enough from the previous one
           * to bother printing it as a separate choice?
           */
          sublen = instring(prev+len, ' ');
          if (strncmp(key->key, prev, len+sublen+1) != 0) {
             /* yup, this is different up to the next space */
             if (!status) {
                /* first one we have printed is special */
                fprintf(stderr,
                       "Ambiguous request '%.*s'; possible matches:\n",
                       len, first);
                fprintf(stderr, "\t%s\n", prev);
                status = TRUE;
             }
             fprintf(stderr, "\t%s\n", key->key);
             prev = key->key;
          }
       }
    }
   
    return(status);
}

/* PrintHelp:
 * print the text for key
 */
 /* (in) - subtopics only? */
 /* (out) - are there subtopics? */
static void
PrintHelp(KEY *key, boolean *subtopics)
{
    LINEBUF *t;
#ifdef MSDOS
    char buf[BUFSIZ];        /* line from help file */
#endif

    StartOutput();

    if (subtopics == NULL || !*subtopics) {
#ifdef MSDOS
       fseek(helpfp,key->pos,0);
       while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
            && (buf[0] != KEYFLAG) ) {
          OutLine(buf);
       }
#else
       for (t = key->text; t != NULL; t = t->next)
        OutLine(t->line);        /* print text line */
#endif
    }

    ShowSubtopics(key, subtopics);
    OutLine("\n");

    EndOutput();
}

/* ShowSubtopics:
 *  Print a list of subtopic names
 */
#define PER_LINE 4

/* the topic */
/* (out) are there any subtopics */
static void
ShowSubtopics(KEY *key, boolean *subtopics)
{
    int subt = 0;            /* printed any subtopics yet? */
    KEY *subkey;            /* subtopic key */
    int len;                /* length of key name */
    char line[BUFSIZ];        /* subtopic output line */
    char *start;            /* position of subname in key name */
    int sublen;            /* length of subname */
    int pos = 0;
    char *prev = NULL;        /* the last thing we put on the list */

    *line = '\0';
    len = strlen(key->key);

    for (subkey = key+1; subkey->key != NULL; subkey++) {
       if (strncmp(subkey->key, key->key, len) == 0) {
          /* find this subtopic name */
          start = subkey->key + len;
          if (len > 0)
            if (*start == ' ')
             start++;        /* skip space */
            else
             break;        /* not the same topic after all  */
          else            /* here we are looking for main topics */
            if (!subkey->primary)
             continue;    /* not a main topic */
          sublen = instring(start, ' ');
          if (prev == NULL || strncmp(start, prev, sublen) != 0) {
             if (subt == 0) {
                subt++;
                if (len) {
                  (void) sprintf(line, "Subtopics available for %s:\n",
				  key->key);
				}
                else {
                  (void) sprintf(line, "Help topics available:\n");
				}
                OutLine(line);
                sprintf(line, "    ");
                pos = 0;
             }
             if (pos == PER_LINE) {
                (void) strcat(line, "\n");
                OutLine(line);
                sprintf(line, "    ");
                pos = 0;
             }
			 {
				char tmp[80];

			 	/*********
             	(void) strcat(line, "\t");
             	(void) strncat(line, start, sublen);
			 	************/
             	(void) strncpy(tmp, start, sublen);
				tmp[sublen] = '\0';
			 	sprintf(line+strlen(line), "%-16s", tmp);
			 }
             pos++;
             prev = start;
          }
       } else {
          /* new topic */
          break;
       }
    }
   
    /* put out the last line */
    if (subt > 0 && pos > 0) {
       (void) strcat(line, "\n");
       OutLine(line);
    }
   
/*
    if (subt == 0) {
       OutLine("\n");
       OutLine("No subtopics available\n");
    }
*/
   
    if (subtopics)
     *subtopics = (subt != 0);
}


/* StartOutput:
 * Open a file pointer to a pipe to user's $PAGER, if there is one,
 * otherwise use our own pager.
 */
static void
StartOutput(void)
{
#if !defined(MSDOS) && !defined(AMIGA)
    /********
    char *pager_name = getenv("PAGER");
    char *pager_name = "more -c";
    if (pager_name != NULL && *pager_name != '\0')
    ******/
    extern char Ft_Pager[];
    extern FILE *popen(const char *, const char *);

    if (*Ft_Pager) {
        if ((help_stream = popen(Ft_Pager, "w")) != (FILE *)NULL)  {
			return;
		}
        fprintf(stderr, "Warning: Could not open pager %s.\n", Ft_Pager);
    }

    help_stream = stderr;
    /* fall through to built-in pager */
#endif

    /* built-in pager */
    pagelines = 0;
}

/* write a line of help output  */
/* line should contain only one \n, at the end */
static void
OutLine(char *line)
{
    int c;                /* dummy input char */
#if !defined(MSDOS) && !defined(AMIGA)
    if (help_stream != stderr) {
       fputs(line, help_stream);
       return;
    }
#endif

    /* built-in dumb pager */
    /* leave room for prompt line */
    if (pagelines >= SCREENSIZE - 2) {
       printf("Press return for more: ");
       do
        c = getchar();
       while (c != EOF && c != '\n');
       pagelines = 0;
    }
    fputs(line, stderr);
    pagelines++;
}

static void
EndOutput(void)
{
#if !defined(MSDOS) && !defined(AMIGA)
    extern int pclose(FILE *);

    if (help_stream != stderr) {
        (void) pclose(help_stream);
	}
#endif
}

/* find char c in string str; return p such that str[p]==c;
 * if c not in str then p=strlen(str)
 */
static int
instring(char *str, char c)
{
    int pos = 0;

    while (str != NULL && *str != '\0' && c != *str) {
       str++;
       pos++;
    }
    return (pos);
}

#define TRUE 1
#define FALSE 0
#include "fudgit.h"
#include "command.h"
extern char *Ft_nextline(char *prompt, int *eof);

/* Ft_help: (not VMS, although it would work)
 * Give help to the user.
 * It parses the command line into helpbuf and supplies help for that
 * string. Then, if there are subtopics available for that key,
 * it prompts the user with this string. If more input is
 * given, Ft_help is called recursively, with the argument the index of
 * null character in the string. Thus a more specific help can be
 * supplied. This can be done repeatedly.
 * If null input is given, the function returns, effecting a
 * backward climb up the tree.
 * David Kotz (David.Kotz@Dartmouth.edu) 10/89
 * Modified by Martin-D. Lacasse (isaac@physics.mcgill.ca) 12/91
 */
/* The first call is done without the "help" string */
int Ft_help(int argc, char **argv, char *l, Command *cmp)
{
    char helpbuf[LINESIZE];
    char prompt[LINESIZE];
    int main;       
    int more_help;
    int subtopics;            /* 0 if no subtopics for this topic */
    char *cp1, *cp2;
    int eof;
	int nargc;
    extern int Ft_Interact;
    extern int Ft_iolevel(void);
	extern int Ft_vgetargp(char *b, char **p, int type, char **add);
	extern char *Ft_nextline(char *prompt, int *eof);

    if (!Ft_Interact || Ft_iolevel()) {
        fprintf(stderr, "%s: Interactive use only.\n", cmp->fname);
        return(ERRR);
    }

    helpbuf[0] = prompt[0] = 0;

    if (argc > 1) { /* take the last arguments only  */
        cp1 = l + strlen(argv[0]) + 1;
		l[strlen(l)-1] = '\0';
        main = 0;
    }   
    else {
        cp1 = "";
        main = 1;
    }

    subtopics = 0;
    more_help = 1;

    switch (help(cp1, HELPFILE, &subtopics)) {
       case H_FOUND: {
          sprintf(helpbuf, "%s", cp1);
          do {
             if (subtopics) {
                /* prompt for subtopic with current help string */
                if (main) {
                  (void) strcpy(prompt,
                  "(Type ? for available topics.)\nHelp topic: ");
				}
                else {
                  (void) sprintf(prompt,
                  "(Type ? for available subtopics.)\nSubtopic of %s: ",
                  helpbuf);
				}
                do {
                    cp2 = Ft_nextline(prompt, &eof);
                } while (cp2 == NULL);
                if (*cp2 == '?') {
                    help(helpbuf, HELPFILE, &subtopics);
                }
                else if (*cp2 != '\n') {
                    sprintf(prompt, "help %s %s", helpbuf, cp2);
                    nargc = Ft_vgetargp(prompt, argv, NOTHING, 0);
                    if (nargc > argc) {
                          Ft_help(nargc, argv, l, cmp);
                    }
                }
                else {
                       more_help = FALSE;
                }
             } else
               more_help = FALSE;
          } while(more_help);
   
          break;
       }
       case H_NOTFOUND: {
          printf("Sorry, no help for '%s'\n", cp1);
          break;
       }
       case H_ERROR: {
          perror(HELPFILE);
          break;
       }
       default: {        /* defensive programming */
          fputs("Ft_help: Impossible case in switch.\n", stderr);
          /* NOTREACHED */
       }
    }
    return(0);
}

