/*
 * Copyright (c) 1990,1994 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 *
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

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

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "printcap.h"

#ifndef BUFSIZ
#define	BUFSIZ	1024
#endif /* ! BUFSIZ */
#define MAXHOP	32	/*!< max number of tc= indirections */

/*!
 * @file
 * @brief routines for dealing with the terminal capability data base based on termcap
 *
 * @bug		Should use a "last" pointer in tbuf, so that searching
 *		for capabilities alphabetically would not be a n**2/2
 *		process when large numbers of capabilities are given.
 * @note	If we add a last pointer now we will screw up the
 *		tc capability. We really should compile termcap.
 *
 * Essentially all the work here is scanning and decoding escapes
 * in string capabilities.  We don't use stdio because the editor
 * doesn't, and because living w/o it is not hard.
 */

#define PRINTCAP

#ifdef PRINTCAP
#define tgetent	pgetent
#define tskip	pskip
#define tgetstr	pgetstr
#define tdecode pdecode
#define tgetnum	pgetnum
#define	tgetflag pgetflag
#define tdecode pdecode
#define tnchktc	pnchktc
#define	tnamatch pnamatch
#define V6
#endif /* PRINTCAP */

static	FILE *pfp = NULL;	/*!< printcap data base file pointer */
static	char *tbuf;
static	int hopcount;		/*!< detect infinite loops in termcap, init 0 */

/*!
 * @brief Similar to tgetent except it returns the next entry instead of
 * doing a lookup.
 *
 * @note Added a "cap" parameter, so we can use these calls for printcap
 * and papd.conf.
 */
int getprent(char *cap, char *bp, int bufsize)
{
    int c;
    int skip = 0;
    int i;

    if (pfp == NULL && (pfp = fopen(cap, "r")) == NULL) {
        return -1;
    }

    tbuf = bp;
    i = 0;

    for (;;) {
        switch (c = getc(pfp)) {
        case EOF:
            if (bp != tbuf) {
                *bp = '\0';
                return 1;
            }

            fclose(pfp);
            pfp = NULL;
            return 0;

        case '\n':
            if (bp == tbuf) {
                skip = 0;
                continue;
            }

            if (bp[-1] == '\\') {
                bp--;
                continue;
            }

            *bp = '\0';
            return 1;

        case '#':
            if (bp == tbuf) {
                skip++;
            }

        default:
            if (skip) {
                continue;
            }

            if (bp >= tbuf + BUFSIZ) {
                fprintf(stderr, "Termcap entry too long\n");
                *bp = '\0';
                return 1;
            }

            *bp++ = c;

            if (++i >= bufsize) {
                fprintf(stderr, "config file too large\n");
                fclose(pfp);
                pfp = NULL;
                *bp = '\0';
                return 1;
            }
        }
    }
}

void endprent(void)
{
    if (pfp != NULL) {
        fclose(pfp);
    }
}

/*!
 * @brief Get an entry for terminal name in buffer bp,
 * from the termcap file.
 * @note Parse is very rudimentary;
 * we just notice escaped newlines.
 *
 * Added a "cap" parameter, so we can use these calls for printcap
 * and papd.conf.
 */
int tgetent(char *cap, char *bp, const char *name)
{
    char *cp;
    int c;
    int i = 0;
    int cnt = 0;
    char ibuf[BUFSIZ];
    int tf;
    int skip;
#ifndef V6
    cp = getenv("TERMCAP");

    /*
     * TERMCAP can have one of two things in it. It can be the
     * name of a file to use instead of /etc/termcap. In this
     * case it better start with a "/". Or it can be an entry to
     * use so we don't have to read the file. In this case it
     * has to already have the newlines crunched out.
     */
    if (cp && *cp) {
        if (*cp != '/') {
            cp2 = getenv("TERM");

            if (cp2 == (char *) 0 || strcmp(name, cp2) == 0) {
                strcpy(bp, cp);
                return tnchktc(cap);
            } else {
                tf = open(cap, 0);
            }
        } else {
            tf = open(cp, 0);
        }
    }

    if (tf == 0) {
        tf = open(cap, 0);
    }

#else /* V6 */
    tf = open(cap, 0);
#endif /* V6 */

    if (tf < 0) {
        return -1;
    }

    for (;;) {
        cp = bp;
        skip = 0;

        for (;;) {
            if (i == cnt) {
                cnt = read(tf, ibuf, BUFSIZ);

                if (cnt <= 0) {
                    close(tf);
                    return 0;
                }

                i = 0;
            }

            c = ibuf[i++];

            if (c == '\n') {
                if (!skip && cp > bp && cp[-1] == '\\') {
                    cp--;
                    continue;
                }

                skip = 0;

                if (cp == bp) {
                    continue;
                } else {
                    break;
                }
            }

            if (c == '#' && cp == bp) {
                skip++;
            }

            if (skip) {
                continue;
            }

            if (cp >= bp + BUFSIZ) {
                fprintf(stderr, "Termcap entry too long\n");
                break;
            } else {
                *cp++ = c;
            }
        }

        *cp = 0;

        /*
         * The real work for the match.
         */
        if (tnamatch(name)) {
            close(tf);
            return tnchktc(cap);
        }
    }
}

/*!
 * @brief check the last entry, see if it's tc=xxx.
 *
 * If so, recursively find xxx and append that entry (minus the names)
 * to take the place of the tc=xxx entry. This allows termcap
 * entries to say "like an HP2621 but doesn't turn on the labels".
 * Note that this works because of the left to right scan.
 *
 * Added a "cap" parameter, so we can use these calls for printcap
 * and papd.conf.
 */
int tnchktc(char *cap)
{
    char *p;
    char *q;
    /* name of similar terminal */
    char tcname[16];
    char *tcbuf = calloc(1, BUFSIZ);

    if (tcbuf == NULL) {
        return 0;
    }

    char *holdtbuf = tbuf;
    int l;
    /* before the last colon */
    p = tbuf + strlen(tbuf) - 2;

    while (*--p != ':')
        if (p < tbuf) {
            fprintf(stderr, "Bad termcap entry\n");
            free(tcbuf);
            return 0;
        }

    p++;

    /* p now points to beginning of last field */
    if (p[0] != 't' || p[1] != 'c') {
        free(tcbuf);
        return 1;
    }

    strcpy(tcname, p + 3);
    q = tcname;

    while (q && *q != ':') {
        q++;
    }

    *q = 0;

    if (++hopcount > MAXHOP) {
        fprintf(stderr, "Infinite tc= loop\n");
        free(tcbuf);
        return 0;
    }

    if (tgetent(cap, tcbuf, tcname) != 1) {
        free(tcbuf);
        return 0;
    }

    for (q = tcbuf; q && *q != ':'; q++)
        ;

    l = p - holdtbuf + strlen(q);

    if (l > BUFSIZ) {
        fprintf(stderr, "Termcap entry too long\n");
        q[BUFSIZ - (p - tbuf)] = 0;
    }

    strcpy(p, q + 1);
    free(tcbuf);
    tbuf = holdtbuf;
    return 1;
}

/*!
 * @brief deals with name matching.
 *
 * The first field of the termcap entry is a sequence of names separated by |'s,
 * so we compare against each such name.
 * The normal : terminator after the last name (before the first field) stops us.
 */
int tnamatch(const char *np)
{
    const char *Np;
    const char *Bp;
    Bp = tbuf;

    if (*Bp == '#') {
        return 0;
    }

    for (;;) {
        for (Np = np; *Np && *Bp == *Np; Bp++, Np++) {
            continue;
        }

        if (*Np == 0 && (*Bp == '|' || *Bp == ':' || *Bp == 0)) {
            return 1;
        }

        while (*Bp && *Bp != ':' && *Bp != '|') {
            Bp++;
        }

        if (*Bp == 0 || *Bp == ':') {
            return 0;
        }

        Bp++;
    }
}

/*!
 * @brief Skip to the next field.
 * Notice that this is very dumb, not knowing about \\: escapes or any such.
 * If necessary, :'s can be put into the termcap file in octal.
 */
static char *tskip(char *bp)
{
    while (*bp && *bp != ':') {
        bp++;
    }

    while (*bp && *bp == ':') {
        bp++;
    }

    return bp;
}

/*!
 * @brief Return the (numeric) option id.
 *
 * Numeric options look like
 * @code
 *	li#80
 * @endcode
 * i.e. the option string is separated from the numeric value by
 * a # character.  If the option is not found we return -1.
 * Note that we handle octal numbers beginning with 0.
 */
int tgetnum(char *id)
{
    int i;
    int base;
    char *bp = tbuf;

    for (;;) {
        bp = tskip(bp);

        if (*bp == 0) {
            return -1;
        }

        if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1]) {
            continue;
        }

        if (*bp == '@') {
            return -1;
        }

        if (*bp != '#') {
            continue;
        }

        bp++;
        base = 10;

        if (*bp == '0') {
            base = 8;
        }

        i = 0;

        while (isdigit(*bp)) {
            i *= base, i += *bp++ - '0';
        }

        return i;
    }
}

/*!
 * @brief Handle a flag option.
 *
 * Flag options are given "naked", i.e. followed by a : or the end
 * of the buffer.
 * @returns 1 if we find the option, or 0 if it is not given.
 */
int tgetflag(char *id)
{
    char *bp = tbuf;

    for (;;) {
        bp = tskip(bp);

        if (!*bp) {
            return 0;
        }

        if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) {
            if (!*bp || *bp == ':') {
                return 1;
            } else if (*bp == '@') {
                return 0;
            }
        }
    }
}

/*!
 * Tdecode does the grunt work to decode the
 * string capability escapes.
 */
static char *
tdecode(char *str, char **area)
{
    char *cp;
    int c;
    const char *dp;
    int i;
    cp = *area;

    while ((c = *str++) && c != ':') {
        switch (c) {
        case '^':
            c = *str++ & 037;
            break;

        case '\\':
            dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
            c = *str++;
nextc:

            if (*dp++ == c) {
                c = *dp++;
                break;
            }

            dp++;

            if (*dp) {
                goto nextc;
            }

            if (isdigit(c)) {
                c -= '0', i = 2;

                do {
                    c <<= 3, c |= *str++ - '0';
                } while (--i && isdigit(*str));
            }

            break;
        }

        *cp++ = c;
    }

    *cp++ = 0;
    str = *area;
    *area = cp;
    return str;
}

/*!
 * @brief Get a string valued option.
 *
 * These are given as
 * @code
 *	cl=^Z
 * @endcode
 * Much decoding is done on the strings, and the strings are
 * placed in area, which is a ref parameter which is updated.
 * No checking on area overflow.
 */
char *
tgetstr(char *id, char **area)
{
    char *bp = tbuf;

    for (;;) {
        bp = tskip(bp);

        if (!*bp) {
            return NULL;
        }

        if (*bp++ != id[0] || *bp == 0 || *bp++ != id[1]) {
            continue;
        }

        if (*bp == '@') {
            return NULL;
        }

        if (*bp != '=') {
            continue;
        }

        bp++;
        return tdecode(bp, area);
    }
}

static char *
decodename(char *str, char **area, int bufsize)
{
    char *cp;
    int c;
    const char *dp;
    int i;
    cp = *area;

    while ((c = *str++) && --bufsize && c != ':' && c != '|') {
        switch (c) {
        case '^':
            c = *str++ & 037;
            break;

        case '\\':
            dp = "E\033^^\\\\::n\nr\rt\tb\bf\f";
            c = *str++;
nextc:

            if (*dp++ == c) {
                c = *dp++;
                break;
            }

            dp++;

            if (*dp) {
                goto nextc;
            }

            if (isdigit(c)) {
                c -= '0', i = 2;

                do {
                    c <<= 3, c |= *str++ - '0';
                } while (--i && isdigit(*str));
            }

            break;
        }

        *cp++ = c;
    }

    *cp++ = 0;
    str = *area;
    *area = cp;
    return str;
}

char *
getpname(char **area, int bufsize)
{
    return decodename(tbuf, area, bufsize);
}
