/* File: main-cap.c */

/* Purpose: Support for "term.c" using "termcap" calls */

#include "angband.h"


#ifdef USE_CAP


/*
 * This file is a total hack.  :-)
 *
 * This file allows use of the terminal without requiring the
 * "curses" routines.  In fact, if "USE_HARDCODE" is defined,
 * this file will attempt to use various hard-coded "vt100"
 * escape sequences to also avoid the use of the "termcap"
 * routines.  I do not know if this will work on System V.
 *
 * This file is intended for use only on those machines which are unable
 * for whatever reason to compile the "main-gcu.c" file, but which seem
 * to be able to support the "termcap" library, or which at least seem
 * able to support "vt100" terminals.
 *
 * Large portions of this file were stolen from "main-gcu.c"
 *
 * This file incorrectly handles output to column 80, I think.
 * The "term.c" file never calls "Term_wipe()" for the screen.
 */


/*
 * Require a "system"
 */
#if !defined(USE_TERMCAP) && !defined(USE_HARDCODE)
# define USE_TERMCAP
#endif

/*
 * Hack -- try to guess which systems use what commands
 * Hack -- allow one of the "USE_Txxxxx" flags to be pre-set.
 * Mega-Hack -- try to guess when "POSIX" is available.
 * If the user defines two of these, we will probably crash.
 */
#if !defined(USE_TPOSIX)
# if !defined(USE_TERMIO) && !defined(USE_TCHARS)
#  if defined(_POSIX_VERSION)
#   define USE_TPOSIX
#  else
#   if defined(USG) || defined(linux) || defined(SOLARIS)
#    define USE_TERMIO
#   else
#    define USE_TCHARS
#   endif
#  endif
# endif
#endif



/*
 * POSIX stuff
 */
#ifdef USE_TPOSIX
# include <sys/ioctl.h>
# include <termios.h>
#endif

/*
 * One version needs these files
 */
#ifdef USE_TERMIO
# include <sys/ioctl.h>
# include <termio.h>
#endif

/*
 * The other needs these files
 */
#ifdef USE_TCHARS
# include <sys/ioctl.h>
# include <sys/resource.h>
# include <sys/param.h>
# include <sys/file.h>
# include <sys/types.h>
#endif


/*
 * XXX XXX Hack -- POSIX uses "O_NONBLOCK" instead of "O_NDELAY"
 * They should both work due to the "(i != 1)" test.
 */
#ifndef O_NDELAY
# define O_NDELAY O_NONBLOCK
#endif




#ifdef USE_TERMCAP

/*
 * Termcap string information
 */

static char blob[1024];		/* The "termcap" entry */
static char area[1024];		/* The string extraction buffer */
static char *next = area;	/* The current "index" into "area" */
static char *desc;		/* The terminal name */

#endif


/*
 * Pointers into the "area"
 */

static char *cm;	/* Move cursor */
static char *ch;	/* Move cursor to horizontal location */
static char *cv;	/* Move cursor to vertical location */
static char *ho;	/* Move cursor to top left */
static char *ll;	/* Move cursor to bottom left */
static char *cs;	/* Set scroll area */
static char *cl;	/* Clear screen */
static char *cd;	/* Clear to end of display */
static char *ce;	/* Clear to end of line */
static char *cr;	/* Move to start of line */
static char *so;	/* Turn on standout */
static char *se;	/* Turn off standout */
static char *md;	/* Turn on bold */
static char *me;	/* Turn off bold */
static char *vi;	/* Cursor - invisible */
static char *ve;	/* Cursor - normal */
static char *vs;	/* Cursor - bright */


/*
 * State variables
 */

static int rows;	/* Screen size (Y) */
static int cols;	/* Screen size (X) */
static int curx;	/* Cursor location (X) */
static int cury;	/* Cursor location (Y) */
static int curv;	/* Cursor visibility */


/*
 * Extern functions
 */
extern char *getenv();
extern char *tgoto();
extern char *tgetstr();


/*
 * Write some chars to the terminal
 */
static void ewrite(char *str)
{
  int numtowrite, numwritten;

  /* See how much work we have */
  numtowrite = strlen(str);

  /* Write until done */
  while (numtowrite > 0)
  {
    /* Try to write the chars */
    numwritten = write(1, str, numtowrite);

    /* Handle FIFOs and EINTR */
    if (numwritten < 0) numwritten = 0;

    /* See what we completed */
    numtowrite -= numwritten;
    str += numwritten;

    /* Hack -- sleep if not done */
    if (numtowrite > 0) sleep(1);
  }
}



#ifdef USE_TERMCAP

static char write_buffer[128];
static char *write_buffer_ptr;

static void output_one(char c)
{
  *write_buffer_ptr++ = c;
}

static void tp(char *s)
{
  /* Dump the string into us */
  write_buffer_ptr = write_buffer;

  /* Write the string with padding */
  tputs (s, 1, output_one);

  /* Finish the string */
  *write_buffer_ptr = '\0';

  /* Dump the recorded buffer */
  ewrite (write_buffer);
}

#endif

#ifdef USE_HARDCODE

static void tp(char *s)
{
  ewrite(s);
}

#endif







/*
 * Clear the screen
 */
static void do_cl(void)
{
  if (cl) tp (cl);
}

/*
 * Clear to the end of the line
 */
static void do_ce(void)
{
  if (ce) tp(ce);
}


/*
 * Set the cursor visibility (0 = invis, 1 = normal, 2 = bright)
 */
static void curs_set(int vis)
{
  char *v = NULL;

  if (!vis) v = vi;
  else if (vis > 1) v = vs ? vs : ve;
  else v = ve ? ve : vs;

  if (v) tp(v);
}



/*
 * Restrict scrolling to within these rows
 */
static void do_cs(int y1, int y2)
{

#ifdef USE_TERMCAP
  if (cs) tp(tgoto(cs, y2, y1));
#endif

#ifdef USE_HARDCODE
  char temp[64];
  sprintf(temp, cs, y1, y2);
  tp (temp);
#endif

}



/*
 * Go to the given screen location directly
 */
static void do_cm(int x, int y)
{

#ifdef USE_TERMCAP
  if (cm) tp(tgoto(cm, x, y));
#endif

#ifdef USE_HARDCODE
  char temp[64];
  sprintf(temp, cm, y+1, x+1);
  tp(temp);
#endif

}


/*
 * Go to the given screen location in a "clever" manner
 * Note that this routine should be made more efficient!
 */
static void do_move(int x1, int y1, int x2, int y2)
{
  /* Hack -- unknown start location */
  if ((x1 == x2) && (y1 == y2)) do_cm(x2,y2);

  /* Left edge */
  else if (x2 == 0)
  {
    if ((y2 <= 0) && ho) tp(ho);
    else if ((y2 >= rows-1) && ll) tp(ll);
    else if ((y2 == y1) && cr) tp(cr);
#if 0
    else if ((y2 == y1+1) && cr && dn) { tp(cr); tp(dn); }
    else if ((y2 == y1-1) && cr && up) { tp(cr); tp(up); }
#endif
    else do_cm(x2,y2);
  }

#if 0
  /* Up/Down one line */
  else if ((x2 == x1) && (y2 == y1+1) && dn) tp(dn);
  else if ((x2 == x1) && (y2 == y1-1) && up) tp(up);
#endif

  /* Default -- go directly there */
  else do_cm(x2,y2);
}




/*
 * Help initialize this file (see below)
 */
errr init_cap_aux(void)
{

#ifdef USE_TERMCAP

  /* Get the terminal name (if possible) */
  desc = getenv("TERM");
  if (!desc) return (1);

  /* Get the terminal info */
  if (tgetent(blob, desc) != 1) return (2);

  /* Get the (initial) columns and rows, or default */
  if ((cols = tgetnum("co")) == -1) cols = 80;
  if ((rows = tgetnum("li")) == -1) rows = 24;

  /* Find out how to move the cursor to a given location */
  cm = tgetstr("cm", &next);
  if (!cm) return (10);

  /* Find out how to move the cursor to a given position */
  ch = tgetstr("ch", &next);
  cv = tgetstr("cv", &next);

  /* Find out how to "home" the screen */
  ho = tgetstr("ho", &next);

  /* Find out how to "last-line" the screen */
  ll = tgetstr("ll", &next);

  /* Find out how to do a "carriage return" */
  cr = tgetstr("cr", &next);
  if (!cr) cr = "\r";

  /* Find out how to clear the screen */
  cl = tgetstr("cl", &next);
  if (!cl) return (11);

  /* Find out how to clear to the end of display */
  cd = tgetstr("cd", &next);

  /* Find out how to clear to the end of the line */
  ce = tgetstr("ce", &next);

  /* Find out how to scroll (set the scroll region) */
  cs = tgetstr("cs", &next);

  /* Find out how to hilite */
  so = tgetstr("so", &next);
  se = tgetstr("se", &next);
  if (!so || !se) so = se = NULL;

  /* Find out how to bold */
  md = tgetstr("md", &next);
  me = tgetstr("me", &next);
  if (!md || !me) md = me = NULL;

  /* Check the cursor visibility stuff */
  vi = tgetstr("vi", &next);
  vs = tgetstr("vs", &next);
  ve = tgetstr("ve", &next);

#endif

#ifdef USE_HARDCODE

  /* Assume some defualt information */
  rows = 24;
  cols = 80;

  /* Clear screen */
  cl = "\033[2J\033[H";	/* --]--]-- */

  /* Clear to end of line */
  ce = "\033[K";	/* --]-- */

  /* Hilite on/off */
  so = "\033[7m";	/* --]-- */
  se = "\033[m";	/* --]-- */

  /* Scroll region */
  cs = "\033[%d;%dr";	/* --]-- */

  /* Move cursor */
  cm = "\033[%d;%dH";	/* --]-- */

#endif

  /* Success */
  return (0);
}







/*
 * Save the "normal" and "angband" terminal settings
 */

#ifdef USE_TPOSIX

static struct termios  norm_termios;

static struct termios  game_termios;

#endif

#ifdef USE_TERMIO

static struct termio  norm_termio;

static struct termio  game_termio;

#endif

#ifdef USE_TCHARS

static struct sgttyb  norm_ttyb;
static struct tchars  norm_tchars;
static struct ltchars norm_ltchars;
static int            norm_local_chars;

static struct sgttyb  game_ttyb;
static struct tchars  game_tchars;
static struct ltchars game_ltchars;
static int            game_local_chars;

#endif



/*
 * Are we active?  Not really needed.
 */
static int active = FALSE;


/*
 * The main screen (no sub-screens)
 */
static term term_screen_body;



/*
 * Place the "keymap" into its "normal" state
 */
static void keymap_norm(void)
{

#ifdef USE_TPOSIX

    /* restore the saved values of the special chars */
    (void)tcsetattr(0, TCSAFLUSH, &norm_termios);

#endif

#ifdef USE_TERMIO

    /* restore the saved values of the special chars */
    (void)ioctl(0, TCSETA, (char *)&norm_termio);

#endif

#ifdef USE_TCHARS

    /* restore the saved values of the special chars */
    (void)ioctl(0, TIOCSETP, (char *)&norm_ttyb);
    (void)ioctl(0, TIOCSETC, (char *)&norm_tchars);
    (void)ioctl(0, TIOCSLTC, (char *)&norm_ltchars);
    (void)ioctl(0, TIOCLSET, (char *)&norm_local_chars);

#endif

}


/*
 * Place the "keymap" into the "game" state
 */
static void keymap_game(void)
{

#ifdef USE_TPOSIX

    /* restore the saved values of the special chars */
    (void)tcsetattr(0, TCSAFLUSH, &game_termios);

#endif

#ifdef USE_TERMIO

    /* restore the saved values of the special chars */
    (void)ioctl(0, TCSETA, (char *)&game_termio);

#endif

#ifdef USE_TCHARS

    /* restore the saved values of the special chars */
    (void)ioctl(0, TIOCSETP, (char *)&game_ttyb);
    (void)ioctl(0, TIOCSETC, (char *)&game_tchars);
    (void)ioctl(0, TIOCSLTC, (char *)&game_ltchars);
    (void)ioctl(0, TIOCLSET, (char *)&game_local_chars);

#endif

}


/*
 * Save the normal keymap
 */
static void keymap_norm_prepare(void)
{

#ifdef USE_TPOSIX

    /* Get the normal keymap */
    tcgetattr(0, &norm_termios);

#endif

#ifdef USE_TERMIO

    /* Get the normal keymap */
    (void)ioctl(0, TCGETA, (char *)&norm_termio);

#endif

#ifdef USE_TCHARS

    /* Get the normal keymap */
    (void)ioctl(0, TIOCGETP, (char *)&norm_ttyb);
    (void)ioctl(0, TIOCGETC, (char *)&norm_tchars);
    (void)ioctl(0, TIOCGLTC, (char *)&norm_ltchars);
    (void)ioctl(0, TIOCLGET, (char *)&norm_local_chars);

#endif

}


/*
 * Save the keymaps (normal and game)
 */
static void keymap_game_prepare(void)
{

#ifdef USE_TPOSIX

    /* Acquire the current mapping */
    tcgetattr(0, &game_termios);

    /* Force "CTRL-C" to interupt */
    game_termios.c_cc[VINTR] = (char)3;

    /* Force "CTRL-Z" to suspend */
    game_termios.c_cc[VSUSP] = (char)26;

    /* Hack -- Leave "VSTART/VSTOP" alone */

    /* Disable the standard control characters */
    game_termios.c_cc[VQUIT] = (char)-1;
    game_termios.c_cc[VERASE] = (char)-1;
    game_termios.c_cc[VKILL] = (char)-1;
    game_termios.c_cc[VEOF] = (char)-1;
    game_termios.c_cc[VEOL] = (char)-1;

    /* Normally, block until a character is read */
    game_termios.c_cc[VMIN] = 1;
    game_termios.c_cc[VTIME] = 0;

    /* Hack -- Turn off "echo" and "canonical" mode */
    game_termios.c_lflag &= ~(ECHO | ICANON);

#endif

#ifdef USE_TERMIO

    /* Acquire the current mapping */
    (void)ioctl(0, TCGETA, (char *)&game_termio);

    /* Force "CTRL-C" to interupt */
    game_termio.c_cc[VINTR] = (char)3;

    /* Force "CTRL-Z" to suspend */
    game_termio.c_cc[VSUSP] = (char)26;

    /* Hack -- Leave "VSTART/VSTOP" alone */

    /* Disable the standard control characters */
    game_termio.c_cc[VQUIT] = (char)-1;
    game_termio.c_cc[VERASE] = (char)-1;
    game_termio.c_cc[VKILL] = (char)-1;
    game_termio.c_cc[VEOF] = (char)-1;
    game_termio.c_cc[VEOL] = (char)-1;

#if 0
    /* Disable the non-posix control characters */
    game_termio.c_cc[VEOL2] = (char)-1;
    game_termio.c_cc[VSWTCH] = (char)-1;
    game_termio.c_cc[VDSUSP] = (char)-1;
    game_termio.c_cc[VREPRINT] = (char)-1;
    game_termio.c_cc[VDISCARD] = (char)-1;
    game_termio.c_cc[VWERASE] = (char)-1;
    game_termio.c_cc[VLNEXT] = (char)-1;
    game_termio.c_cc[VSTATUS] = (char)-1;
#endif

    /* Normally, block until a character is read */
    game_termio.c_cc[VMIN] = 1;
    game_termio.c_cc[VTIME] = 0;

    /* Hack -- Turn off "echo" and "canonical" mode */
    game_termio.c_lflag &= ~(ECHO | ICANON);

#endif

#ifdef USE_TCHARS

    /* Get the default game characters */
    (void)ioctl(0, TIOCGETP, (char *)&game_ttyb);
    (void)ioctl(0, TIOCGETC, (char *)&game_tchars);
    (void)ioctl(0, TIOCGLTC, (char *)&game_ltchars);
    (void)ioctl(0, TIOCLGET, (char *)&game_local_chars);

    /* Force interupt (^C) */
    game_tchars.t_intrc = (char)3;

    /* Force start/stop (^Q, ^S) */
    game_tchars.t_startc = (char)17;
    game_tchars.t_stopc = (char)19;

    /* Cancel some things */
    game_tchars.t_quitc = (char)-1;
    game_tchars.t_eofc = (char)-1;
    game_tchars.t_brkc = (char)-1;

    /* Force suspend (^Z) */
    game_ltchars.t_suspc = (char)26;

    /* Cancel some things */
    game_ltchars.t_dsuspc = (char)-1;
    game_ltchars.t_rprntc = (char)-1;
    game_ltchars.t_flushc = (char)-1;
    game_ltchars.t_werasc = (char)-1;
    game_ltchars.t_lnextc = (char)-1;

    /* XXX XXX XXX XXX XXX Verify this before use */
    /* Hack -- Turn off "echo" and "canonical" mode */
    /* game_termios.c_lflag &= ~(ECHO | ICANON); */
    game_ttyb.flag &= ~(ECHO | ICANON);

#endif

}








/*
 * Suspend/Resume
 */
static errr Term_xtra_cap_level(int v)
{
    /* Suspend */
    if (v == TERM_LEVEL_HARD_SHUT)
    {
        if (!active) return (1);

        /* Hack -- make sure the cursor is visible */
        curs_set(1);

        /* Move to bottom right */
        do_move(0, rows - 1, 0, rows - 1);

        /* Go to normal keymap mode */
        keymap_norm();

        /* No longer active */
        active = FALSE;
    }

    /* Resume */
    else if (v == TERM_LEVEL_HARD_OPEN)
    {
        if (active) return (1);

        /* Hack -- restore the cursor location */
        do_move(curx, cury, curx, cury);

        /* Hack -- restore the cursor visibility */
        curs_set(curv);

        /* Go to angband keymap mode */
        keymap_game();

        /* Now we are active */
        active = TRUE;
    }

    /* Success */
    return (0);
}



/*
 * Check for events
 */
static errr Term_xtra_cap_check(int v)
{
    int i, arg;
    char buf[2];

    /* Get the current flags for stdin */
    arg = fcntl(0, F_GETFL, 0);

    /* Oops */
    if (arg < 0) return (1);

    /* Tell stdin not to block */
    if (fcntl(0, F_SETFL, arg | O_NDELAY) < 0) return (1);

    /* Read one byte, if possible */
    i = read(0, buf, 1);

    /* Replace the flags for stdin */
    if (fcntl(0, F_SETFL, arg)) return (1);

    /* No keys ready */
    if ((i != 1) || (!buf[0])) return (1);

    /* Enqueue the keypress */
    Term_keypress(buf[0]);

    /* Success */
    return (0);
}


/*
 * Wait for an event (not necessarily a keypress).
 */
static errr Term_xtra_cap_event(int v)
{
    int i;
    char buf[2];

    /* Wait for one byte */
    i = read(0, buf, 1);

    /* Hack -- Handle "errors" */
    if ((i <= 0) && (errno != EINTR)) exit_game_panic();

    /* Enqueue valid keypresses */
    if ((i == 1) && (buf[0])) Term_keypress(buf[0]);

    /* Success */
    return (0);
}




/*
 * Actually move the hardware cursor
 */
static errr Term_curs_cap(int x, int y, int z)
{
    /* Literally move the cursor */
    do_move(curx, cury, x, y);

    /* Save the cursor location */
    curx = x;
    cury = y;

    /* Success */
    return (0);
}


/*
 * Erase a grid of space
 */
static errr Term_wipe_cap(int x, int y, int w, int h)
{
    int dx, dy;

    if (!x && !y && (w >= 80) && (h >= 24))
    {
        do_cl();
        do_move(0, 0, 0, 0);
    }

    else if (w >= 80)
    {
        for (dy = 0; dy < h; ++dy)
        {
            Term_curs_cap(x, y+dy, 0);
            do_ce();
        }
    }

    else
    {
        for (dy = 0; dy < h; ++dy)
        {
            Term_curs_cap(x, y+dy, 0);

            for (dx = 0; dx < w; ++dx)
            {
                putc(' ', stdout);
                curx++;
            }
        }
    }

    /* Hack -- Fix the cursor */
    Term_curs_cap(x, y, curv);

    /* Success */
    return (0);
}


/*
 * Place some text on the screen using an attribute
 */
static errr Term_text_cap(int x, int y, int n, byte a, cptr s)
{
    int i;

    /* Move the cursor */
    Term_curs_cap(x, y, curv);

    /* Dump the text, advance the cursor */
    for (i = 0; s[i]; i++) {

        /* Dump the char */
        putc(s[i], stdout);

        /* Advance cursor 'X', and wrap */
        if (++curx >= cols) {

            /* Reset cursor 'X' */
            curx = 0;

            /* Hack -- Advance cursor 'Y', and wrap */
            if (++cury == rows) cury = 0;
        }
    }

    /* Success */
    return (0);
}


/*
 * Handle a "special request"
 */
static errr Term_xtra_cap(int n, int v)
{
    /* Analyze the request */
    switch (n)
    {
        /* Make a noise */
        case TERM_XTRA_NOISE: (void)write(1, "\007", 1); return (0);

        /* Make the cursor invisible */
        case TERM_XTRA_INVIS: curv = 0; curs_set(0); return (0);

        /* Make the cursor visible */
        case TERM_XTRA_BEVIS: curv = 1; curs_set(1); return (0);

        /* Suspend/Resume curses */
        case TERM_XTRA_LEVEL: return (Term_xtra_cap_level(v));

        /* Check for event */
        case TERM_XTRA_CHECK: return (Term_xtra_cap_check(v));

        /* Wait for event */
        case TERM_XTRA_EVENT: return (Term_xtra_cap_event(v));
    }

    /* Not parsed */
    return (1);
}




/*
 * Init a "term" for this file
 */
static void Term_init_cap(term *t)
{
    if (active) return;

    /* Assume cursor at top left */
    curx = 0;
    cury = 0;
    
    /* Assume visible cursor */
    curv = 1;
    
    /* Clear the screen */
    do_cl();

    /* Hack -- visible cursor */
    curs_set(1);

    /* Assume active */
    active = TRUE;
}


/*
 * Nuke a "term" for this file
 */
static void Term_nuke_cap(term *t)
{
    if (!active) return;

    /* Hack -- make sure the cursor is visible */
    curs_set(1);

    /* Move to bottom right */
    do_move(0, rows - 1, 0, rows - 1);

    /* Normal keymap */
    keymap_norm();

    /* No longer active */
    active = FALSE;
}










/*
 * Prepare this file for Angband usage
 */
errr init_cap(void)
{
    term *t = &term_screen_body;


    /*** Initialize ***/

    /* Initialize the screen */
    if (init_cap_aux()) return (-1);

    /* Hack -- Require large screen, or Quit with message */
    if ((rows < 24) || (cols < 80)) quit("Screen too small!");


    /*** Prepare to play ***/

    /* Extract the normal keymap */
    keymap_norm_prepare();

    /* Extract the game keymap */
    keymap_game_prepare();

    /* Hack -- activate the game keymap */
    keymap_game();

    /* Hack -- Do NOT buffer stdout */
    setbuf(stdout, NULL);


    /*** Now prepare the term ***/

    /* Initialize the term */
    term_init(t, 80, 24, 64);

    /* Hack -- shutdown hook */
    t->init_hook = Term_init_cap;
    t->nuke_hook = Term_nuke_cap;

    /* Stick in some hooks */
    t->text_hook = Term_text_cap;
    t->wipe_hook = Term_wipe_cap;
    t->curs_hook = Term_curs_cap;
    t->xtra_hook = Term_xtra_cap;

    /* Save the term */
    term_screen = t;

    /* Activate it */
    Term_activate(term_screen);

    /* Success */
    return (0);
}


#endif /* USE_CAP */


