/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1986 Wayne A. Christopher, U. C. Berkeley CAD Group
$Id: terminal.c,v 1.18 2010/09/10 20:22:29 h_vogt Exp $
**********/

/*
 * Routines to handle "more"d output.  There are some serious system
 * dependencies in here, and it isn't clear that versions of this stuff
 * can be written for every possible machine...
 */
#include "ngspice.h"
#include "config.h"

#include <stdarg.h>

#ifdef HAVE_SGTTY_H
#include <sgtty.h>
#endif

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#ifdef HAVE_ASPRINTF
#ifdef HAVE_LIBIBERTY_H /* asprintf */
#include <libiberty.h>
#undef AND /* obsolete macro in ansidecl.h */
#elif defined(__MINGW32__)/* we have asprintf, but not libiberty.h */
#include <stdarg.h>
extern int asprintf(char **out, const char *fmt, ...);
extern int vasprintf(char **out, const char *fmt, va_list ap);
#endif
#endif

#if 0
/* Bad interaction with bool type in bool.h because curses also
   defines this symbol. */
#ifdef HAVE_TERMCAP
#include <curses.h>
#include <term.h>
#endif
#endif

#ifdef HAVE_TERMCAP_H
#include <termcap.h>
#elif HAVE_NCURSES_TERMCAP_H
#include <ncurses/termcap.h>
#endif

#include <cpdefs.h>

#include "variable.h"
#include "terminal.h"

/* out_printf doesn't handle double arguments correctly, so we
    sprintf into this buf and call out_send w/ it */
char out_pbuf[8*BSIZE_SP];

bool out_moremode = TRUE;
bool out_isatty = TRUE;

#ifndef TCL_MODULE

#ifdef HAVE_TERMCAP

static char *motion_chars;
static char *clear_chars;
static char *home_chars;
static char *cleol_chars;
#endif /* HAVE_TERMCAP */

#define DEF_SCRHEIGHT   24
#define DEF_SCRWIDTH    80

static int xsize, ysize;
static int xpos, ypos;
static bool noprint, nopause;



/* Start output... */

void
out_init(void)
{
#ifdef TIOCGWINSZ
    struct winsize ws;
#endif

    noprint = nopause = FALSE;

    if (cp_getvar("nomoremode", CP_BOOL, NULL))
        out_moremode = FALSE;
    else
    out_moremode = TRUE;
    if (!out_moremode || !cp_interactive)
        out_isatty = FALSE;

    if (!out_isatty)
        return;

    xsize = ysize = 0;

    /* Figure out the screen size.  We try, in order, TIOCGSIZE,
     * tgetent(), and cp_getvar(height).  Default is 24 x 80.
     */

#ifdef TIOCGWINSZ
    if (!xsize || !ysize) {
        (void) ioctl(fileno(stdout), TIOCGWINSZ, (char *) &ws);
        xsize = ws.ws_col;
        ysize = ws.ws_row;
    }
#endif

    if (!xsize)
        (void) cp_getvar("width", CP_NUM, (char *) &xsize);
    if (!ysize)
        (void) cp_getvar("height", CP_NUM, (char *) &ysize);

    if (!xsize)
        xsize = DEF_SCRWIDTH;
    if (!ysize)
        ysize = DEF_SCRHEIGHT;
    ysize -= 2; /* Fudge room... */
    xpos = ypos = 0;

    return;
}

/* Putc may not be buffered (sp?), so we do it ourselves. */

static char staticbuf[BUFSIZ];
struct {
    int count;
    char *ptr;
} ourbuf = { BUFSIZ, staticbuf };

/* send buffer out */
void
outbufputc(void)
{

    if (ourbuf.count != BUFSIZ) {
      fputs(staticbuf, cp_out);
      memset(staticbuf, 0, BUFSIZ-ourbuf.count);
      ourbuf.count = BUFSIZ;
      ourbuf.ptr = staticbuf;
    }

}

static void
bufputc(char c)
{
    if (--ourbuf.count >= 0) {
	*ourbuf.ptr++ = c;
    } else {
	/* Flush and reset the buffer */
	outbufputc();
	/* and store the character. */
	ourbuf.count--;
	*ourbuf.ptr++ = c;
    }
}


/* prompt for a return */
void
promptreturn(void)
{
    char buf[16];
moe:
    fprintf(cp_out,
        "\n\t-- hit return for more, ? for help -- ");
    if (!fgets(buf, 16, cp_in)) {
        clearerr(cp_in);
        *buf = 'q';
    }
    switch (*buf) {
        case '\n':
            break;
        case 'q':
            noprint = TRUE;
            break;
        case 'c':
            nopause = TRUE;
            break;
        case ' ':
            break;
        case '?':
            fprintf(cp_out,
"\nPossible responses:\n\
\t<cr>   : Print another screenful\n\
\tq <cr> : Discard the rest of the output\n\
\tc <cr> : Continuously print the rest of the output\n\
\t? <cr> : Print this help message\n");
            goto moe;
        default:
            fprintf(cp_out, "Character %d is no good\n", *buf);
            goto moe;
    }

}

/* Print a string to the output.  If this would cause the screen to scroll,
 * print "more".
 */

void
out_send(char *string)
{

    if (noprint)
        return;
    if (!out_isatty || nopause) {
        fputs(string, cp_out);
        return;
    }
    while (*string) {
        switch (*string) {
            case '\n':
                xpos = 0;
                ypos++;
                break;
            case '\f':
                ypos = ysize;
                xpos = 0;
                break;
            case '\t':
                xpos = xpos / 8 + 1;
                xpos *= 8;
                break;
            default:
                xpos++;
                break;
        }
        while (xpos >= xsize) {
            xpos -= xsize;
            ypos++;
        }
        if (ypos >= ysize) {
            outbufputc();       /* out goes buffer */
            promptreturn();
            (void) fflush(cp_out);
            ypos = xpos = 0;
        }
        bufputc(*string);   /* we need to buffer these */
        string++;
    }
    (void) outbufputc();
    return;
}

/* Printf some stuff using more mode. */

void
out_printf(char *fmt, ...)
{
#if defined(HAVE_ASPRINTF) /* seems the best solution */
    char * tbuf;
    va_list ap;
    va_start (ap, fmt);
    vasprintf(&tbuf, fmt, ap);
    va_end (ap);
    out_send(tbuf);
    FREE(tbuf);
#elif defined(HAVE_SNPRINTF) /* the second best */
    va_list ap;
    va_start (ap, fmt);
    vsnprintf(out_pbuf, sizeof(out_pbuf), fmt, ap);
    va_end (ap);
    out_send(out_pbuf);
#else /* guaranteed a bug for long messages */
    va_list ap;
    va_start (ap, fmt);
    vsprintf(out_pbuf, fmt, ap);
    va_end (ap);
    out_send(out_pbuf);
#endif
    return;
}

#ifdef HAVE_TERMCAP
static int
outfn(int c)
{
	putc(c, stdout);
	return c;
}
#endif /* HAVE_TERMCAP */

void 
tcap_init(void)
{
    char *s;
#ifdef HAVE_TERMCAP
    char tbuf[1025];
    static char buf2[100];
    char *charbuf;

    charbuf = buf2;

    if ((s = getenv("TERM"))) {
	if (tgetent(tbuf, s) != -1) {
	    xsize = tgetnum("co");
	    ysize = tgetnum("li");
	    if ((xsize <= 0) || (ysize <= 0))
		xsize = ysize = 0;
	    clear_chars = (char *) tgetstr("cl", &charbuf);
	    motion_chars = (char *) tgetstr("cm", &charbuf);
	    home_chars = (char *) tgetstr("ho", &charbuf);
	    cleol_chars = (char *) tgetstr("ce", &charbuf);
	}
    }
#endif

    if (!xsize) {
        if ((s = getenv("COLS")))
            xsize = atoi(s);
        if (xsize <= 0)
            xsize = 0;
    }

    if (!ysize) {
        if ((s = getenv("LINES")))
            ysize = atoi(s);
        if (ysize <= 0)
            ysize = 0;
    }
}


void 
term_clear(void)
{
#ifdef HAVE_TERMCAP
    if (*clear_chars)
	tputs(clear_chars, 1, outfn);
    else
	fputs("\n", stdout);
#endif
}


void 
term_home(void)
{
#ifdef HAVE_TERMCAP
    if (*home_chars)
	tputs(home_chars, 1, outfn);
    else if (*motion_chars)
	tputs(tgoto(motion_chars, 1, 1), 1, outfn);
    else
	fputs("\n", stdout);
#endif
}


void 
term_cleol(void)
{
#ifdef HAVE_TERMCAP
    if (*cleol_chars)
	tputs(cleol_chars, 1, outfn);
#endif
}

#else

void out_init(void) {}
void outbufputc(void) {}
void promptreturn(void) {}
void term_clear(void) {}
void term_home(void) {}
void term_cleol(void) {}
void tcap_init(void) {}

void out_send(char *string) {fprintf(cp_out,string);}

void
out_printf(char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  vfprintf(cp_out, fmt, ap);
  va_end (ap);
}

#endif /* TCL_MODULE */
