/* Copyright (C) The libssh2 project and its contributors.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

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

#include <starlet.h>
#include <lib$routines.h>
#include <ssdef.h>
#include <descrip.h>
#include <rms.h>

typedef struct manl {
    struct manl *next;
    char *filename;
} man, *manPtr;

typedef struct pf_fabnam {
    struct FAB dfab;
    struct RAB drab;
    struct namldef dnam;
    char   expanded_filename[NAM$C_MAXRSS + 1];
} pfn, *pfnPtr;

/*----------------------------------------------------------*/

fpcopy(char *output, char *input, int len)
{
    char *is, *os;
    int i;

    if(len) {
        for(is = input, os = output, i = 0; i < len; ++i, ++is, ++os) {
            *os = *is;
        }
        *os = 0;
    }
    else {
        output[0] = 0;
    }
}


/*----------------------------------------------------------*/
/* give part of ilename in partname. See code for proper
   value of i ( 0 = node, 1 = dev, 2 = dir, 3 = name etc.
*/

int fnamepart(char *inputfile, char *part, int whatpart)
{
    pfnPtr pf;
    int    status;
    char   ipart[6][256], *i, *p;

    pf = calloc(1, sizeof(pfn));

    pf->dfab = cc$rms_fab;
    pf->drab = cc$rms_rab;
    pf->dnam = cc$rms_naml;

    pf->dfab.fab$l_naml = &pf->dnam;

    pf->dfab.fab$l_fna = (char *) -1;
    pf->dfab.fab$l_dna = (char *) -1;
    pf->dfab.fab$b_fns = 0;
    pf->dfab.fab$w_ifi = 0;

    pf->dnam.naml$l_long_defname = NULL;  /* inputfile; */
    pf->dnam.naml$l_long_defname_size = 0; /* strlen(inputfile); */

    pf->dnam.naml$l_long_filename = inputfile;
    pf->dnam.naml$l_long_filename_size = strlen(inputfile);

    pf->dnam.naml$l_long_expand = pf->expanded_filename;
    pf->dnam.naml$l_long_expand_alloc = NAM$C_MAXRSS;

    pf->dnam.naml$b_nop |= NAML$M_SYNCHK | NAML$M_PWD;

    status = sys$parse(&pf->dfab, 0, 0);
    if(!(status&1)) {
        free(pf);
        return status;
    }

    fpcopy(ipart[0], pf->dnam.naml$l_long_node,
                     pf->dnam.naml$l_long_node_size);
    fpcopy(ipart[1], pf->dnam.naml$l_long_dev,
                     pf->dnam.naml$l_long_dev_size);
    fpcopy(ipart[2], pf->dnam.naml$l_long_dir,
                     pf->dnam.naml$l_long_dir_size);
    fpcopy(ipart[3], pf->dnam.naml$l_long_name,
                     pf->dnam.naml$l_long_name_size);
    fpcopy(ipart[4], pf->dnam.naml$l_long_type,
                     pf->dnam.naml$l_long_type_size);
    fpcopy(ipart[5], pf->dnam.naml$l_long_ver,
                     pf->dnam.naml$l_long_ver_size);

    for(i = ipart[whatpart], p = part; *i; ++i, ++p) {
        if(p == part) {
            *p = toupper(*i);
        }
        else {
            *p = tolower(*i);
        }
    }
    *p = 0;

    free(pf);
    return 1;
}
/*----------------------------------------------------------*/

int find_file(char *filename, char *gevonden, int *findex)
{
    int     status;
    struct  dsc$descriptor gevondend;
    struct  dsc$descriptor filespec;
    char    gevonden_file[NAM$C_MAXRSS + 1];

    filespec.dsc$w_length = strlen(filename);
    filespec.dsc$b_dtype  = DSC$K_DTYPE_T;
    filespec.dsc$b_class  = DSC$K_CLASS_S;
    filespec.dsc$a_pointer = filename;

    gevondend.dsc$w_length = NAM$C_MAXRSS;
    gevondend.dsc$b_dtype  = DSC$K_DTYPE_T;
    gevondend.dsc$b_class  = DSC$K_CLASS_S;
    gevondend.dsc$a_pointer = gevonden_file;

    status = lib$find_file(&filespec, &gevondend, findex, 0, 0, 0, 0);

    if((status & 1) == 1) {
        /* !checksrc! disable BANNEDFUNC 1 */ /* FIXME */
        strcpy(gevonden, strtok(gevonden_file, " "));
    }
    else {
         gevonden[0] = 0;
    }

    return status;
}


/*--------------------------------------------*/

manPtr addman(manPtr *manroot, char *filename)
{
    manPtr m, f;

    m = calloc(1, sizeof(man));
    if(!m)
        return NULL;

    m->filename = strdup(filename);

    if(!*manroot) {
        *manroot = m;
    }
    else {
        for(f = *manroot; f->next; f = f->next)
            ;
        f->next = m;
    }
    return m;
}

/*--------------------------------------------*/
void freeman(manPtr *manroot)
{
    manPtr m, n;

    for(m = *manroot; m; m = n) {
        free(m->filename);
        n = m->next;
        free(m);
    }
    *manroot = NULL;
}

/*--------------------------------------------*/

int listofmans(char *filespec, manPtr *manroot)
{
    manPtr  r;
    int     status;
    int     ffindex = 0;
    char    gevonden[NAM$C_MAXRSS + 1];

    for(;;) {
        status = find_file(filespec, gevonden, &ffindex);

        if((status&1)) {
            r = addman(manroot, gevonden);
            if(!r)
                return 2;
        }
        else {
            if(!(status&1))
                break;
        }
    }

    lib$find_file_end(&ffindex);
    if(status == RMS$_NMF)
        status = 1;

    return status;
}

/*--------------------------------------------*/

int convertman(char *filespec, FILE *hlp, int base_level, int add_parentheses)
{
    FILE    *man;
    char    *in, *uit;
    char    *m, *h;
    size_t  len, thislen, maxlen = 50000;
    int     bol, mode, return_status = 1;
    char subjectname[NAM$C_MAXRSS + 1];

    in  = calloc(1, maxlen + 1);
    uit = calloc(1, maxlen + 1);

    if(!in || !uit)
        return 2;

    man = fopen(filespec, "r");
    if(!man)
        return vaxc$errno;

    for(len = 0; !feof(man) && len < maxlen; len += thislen) {
        thislen = fread(in + len, 1, maxlen - len, man);
    }

    fclose(man);

    m = in;
    h = uit;

    *(m + len) = 0;

    for(mode = 0, bol = 1; *m; ++m) {

        switch(mode) {
        case 0:
            switch(*m) {
            case '.':
                if(bol) {
                    mode = 1;
                }
                else {
                    *h = *m;
                    ++h;
                }
                break;
            case '\\':
                if(bol) {
                    *h = ' '; ++h;
                    *h = ' '; ++h;
                }
                mode = 2;
                break;
            default:
                if(bol) {
                    *h = ' '; ++h;
                    *h = ' '; ++h;
                }
                *h = *m;
                ++h;
                break;
            }
            break;

        case 1: /* after . at bol */
            switch(*m) {
            case '\\':
                while(*m != '\n' && *m != '\r' && *m)
                    ++m;
                mode = 0;
                break;
            case 'B':
                ++m;
                *h = ' '; ++h;
                mode = 0;
                break;
            case 'I':
                /* remove preceding eol */
                if(*(m + 1) != 'P') {
                    --h;
                    while((*h == '\n' || *h == '\r') && h > uit)
                        --h;
                    ++h;
                }

                /* skip .Ix */
                for(; *m != ' ' && *m != '\n' && *m != '\r'; ++m)
                    ;

                /* copy line up to EOL */

                for(; *m != '\n' && *m != '\r' && *m; ++m, ++h)
                    *h = *m;

                /* if line ends in ., this is an EOL */

                if(*(h-1) == '.') {
                    --h;
                    --m;
                }
                else {
                    /* if line does not end in ., skip EOL in source */

                    if(*(m + 1) == '\n' || *(m + 1) == '\r')
                        ++m;
                }
                mode = 0;
                break;
            case 'S':
                if(*(m + 1) == 'H') {
                    *h = '\n';++h;
                    if(strncmp(m + 3, "NAME", 4) == 0 ||
                       strncmp(m + 3, "SYNOPSIS", 8) == 0 ||
                       strncmp(m + 3, "DESCRIPTION", 11) == 0) {
                        while(*m != '\n' && *m != '\r')
                            ++m;
                        mode = 0;
                    }
                    else {
                        ++m;

                        /* write help level, and flag it */

                        *h = '0' + base_level + 1; ++h;
                        return_status |= 2;

                        *h = ' '; ++h;

                        /* skip H (or whatever after S) and blank */
                        ++m; ++m;

                        for(; *m != '\n' && *m != '\r' && *m; ++m, ++h) {

                           /* write help label in lowercase, skip quotes */
                           /* fill blanks with underscores */

                           if(*m != '\"') {
                               *h = tolower(*m);
                               if(*h == ' ')
                                   *h = '_';
                           }
                           else {
                               --h;
                           }
                        }

                        /* Add a linefeed or two */

                        *h = *m; ++h;
                        *h = *m; ++h;

                        mode = 0;
                    }
                }
                break;
            case 'T':
                if(*(m + 1) == 'H') {
                    *h = '0' + base_level; ++h;
                    return_status |= 2;
                    *h = ' '; ++h;
                    for(m = m + 3; *m != ' ' && *m; ++m, ++h) {
                        *h = *m;
                    }
                    if(add_parentheses) {
                        *h = '('; ++h;
                        *h = ')'; ++h;
                    }
                    while(*m != '\n' && *m != '\r' && *m)
                        ++m;
                    mode = 0;
                }
                break;
            default:
                ++m;
                mode = 0;
                break;
            }
            break;
        case 2: /* after \ skip two characters or print the backslash */
            switch(*m) {
                case '\\':
                    *h = *m;
                    ++h;
                    mode = 0;
                    break;
                default:
                    ++m;
                    mode = 0;
                    break;
            }
            break;
        } /* end switch mode */

        bol = 0;
        if(*m == '\n' || *m == '\r')
            bol = 1;

    } /* end for mode */

    *h = 0;


    if(return_status & 2) {
        fprintf(hlp, "%s\n\n", uit);
    }
    else {
        fnamepart(filespec, subjectname, 3);
        if(*subjectname) {
            fprintf(hlp, "%d %s\n\n%s\n\n", base_level, subjectname, uit);
        }
        else {
            /* No filename (as is the case with a logical),
               use first word as subject name */
            char *n, *s;

            for(n = in; isspace(*n); ++n)
                ;
            for(s = subjectname; !(isspace(*n)); ++n, ++s)
                *s = *n;
            *s = 0;

            fprintf(hlp, "%d %s\n\n%s\n\n", base_level, subjectname, uit);
        }
    }

    /*
     printf("read %d from %s, written %d to helpfile, return_status = %d\n",
        len, filespec, strlen(uit), return_status);
    */

    free(m);
    free(h);

    return 1;
}

/*--------------------------------------------*/

int convertmans(char *filespec, char *hlpfilename, int base_level, int append,
                int add_parentheses)
{
    int status = 1;
    manPtr  manroot = NULL, m;
    FILE    *hlp;

    if(append) {
        hlp = fopen(hlpfilename, "a+");
    }
    else {
        hlp = fopen(hlpfilename, "w");
    }

    if(!hlp)
        return vaxc$errno;

    status = listofmans(filespec, &manroot);
    if(!(status&1))
        return status;

    for(m = manroot; m; m = m->next) {
        status = convertman(m->filename, hlp, base_level, add_parentheses);
        if(!(status&1)) {
            fprintf(stderr, "Convertman of %s went wrong\n", m->filename);
            break;
        }
    }
    freeman(&manroot);
    return status;
}

/*--------------------------------------------*/
void print_help(void)
{
    fprintf(stderr,
        "Usage: [-a] [-b x] convertman <manfilespec> <helptextfile>\n"
        "       -a append <manfilespec> to <helptextfile>\n"
        "       -b <baselevel> if no headers found create one "
                   "with level <baselevel>\n"
        "          and the filename as title.\n"
        "       -p add parentheses() to baselevel help items.\n");
}
/*--------------------------------------------*/

main(int argc, char **argv)
{
    int     status;
    int     i, j;
    int     append, base_level, basechange, add_parentheses;
    char    *manfile = NULL;
    char    *helpfile = NULL;

    if(argc < 3) {
        print_help();
        return 1;
    }

    append     = 0;
    base_level = 1;
    basechange = 0;
    add_parentheses = 0;

    for(i = 1; i < argc; ++i) {
        if(argv[i][0] == '-') {
            for(j = 1; argv[i][j]; ++j) {
                switch(argv[i][j]) {
                    case 'a':
                        append = 1;
                        break;
                    case 'b':
                        if((i + 1) < argc) {
                            base_level = atoi(argv[i + 1]);
                            basechange = 1;
                        }
                        break;
                    case 'p':
                        add_parentheses = 1;
                        break;
                }
            }
            if(basechange) {
                basechange = 0;
                i = i + 1;
            }
        }
        else {
            if(!manfile) {
                manfile = strdup(argv[i]);
            }
            else if(!helpfile) {
                helpfile = strdup(argv[i]);
            }
            else {
                fprintf(stderr, "Unrecognized parameter : %s\n", argv[i]);
            }
        }
    }

    /*
    fprintf(stderr,"manfile: %s, helpfile: %s, append: %d, base_level : %d\n",
            manfile, helpfile, append, base_level);
    */

    status = convertmans(manfile, helpfile, base_level, append,
                         add_parentheses);

    free(manfile);
    free(helpfile);

    return status;
}
