/*{{{}}}*/
/*{{{  #includes*/
#ifdef CONFIG_H
#   include "config.h"
#endif

#include <sys/types.h>
#include <unistd.h>
#ifdef TERMINFO
#  ifdef INC_CURSES
#     include <curses.h>
#  endif
#  ifdef INC_TERM
#     include <term.h>
#  endif
#  define TERM_DATA_BASE "terminfo"
#else
#  include <termcap.h>
#  define TERM_DATA_BASE "termcap"
#endif
#ifdef TIOCGWINSZ
#  include <sys/ioctl.h>
#endif
#include <termios.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define SCREEN_C
#define I_BUFFLOOP_C
#define I_DISPLAY_C
#define I_GETMSG_C
#define I_GETTK_C
#define I_KEYBOARD_C
#define I_MAIN_C
#define I_MESSAGES_C
#define I_SIGNALS_C

#include "exit.h"
#include "origami.h"
#include <h/envvar_str.h>
/*}}}  */

/*{{{  variable declarations*/
private boolean so_active=False;
private boolean so_wished=False;
public char const *terminal=0;
/*{{{  termcap data*/
/*{{{  needed capabilities*/
private char const *term_do_cm;
private char const *term_do_ho;
private char const *term_do_do;
private char const *term_do_n_do;
private char const *term_do_ri;
private char const *term_do_n_ri;
/*}}}  */
/*{{{  optional*/
private char const *term_do_ti;
private char const *term_do_te;
private char const *term_do_ks;
private char const *term_do_ke;
private char const *term_do_cd;
private char const *term_do_cl;
public char const *ce;
private char const *term_do_n_ic;
private char const *term_do_n_dc;
public char const *al;
public char const *dl;
private char const *do_AL;
private char const *do_DL;
private char const *term_do_bl;
private char const *term_do_vb;
private char const *term_do_se;
public char const *so;
private char const *term_do_rp;
#ifdef USE_SCRBUFF
# ifndef BLOCK_FLUSH
     private char const *term_do_im;
     private char const *term_do_ic;
     private char const *term_do_ip;
     private char const *term_do_ei;
     private char const *term_do_dc;
     private char const *term_do_le;
     private char const *term_do_LE;
     private char const *term_do_up;
     private char const *term_do_n_up;
     public boolean mi;
# endif
  private char const *term_do_vi;
  private char const *term_do_vs;
#endif
public boolean am,hz,xs,xt;
public int sg;
/*}}}  */
/*}}}  */
#if defined(USE_SCRBUFF) && !defined(BLOCK_FLUSH)
  public boolean use_shift;
#endif
public FILE *scrout;
public win_data screen;
/*}}}  */

/*{{{  t_param_1,t_param_2*/
#ifdef TERMINFO
#  define USE_TPARM
#endif
#ifdef USE_TPARM
# ifdef TPARM_NEEDS_2
#    define t_param_1(f,x1)    (f?(char*)tparm(f,x1,0):(char*)0)
# else
#    define t_param_1(f,x1)    (f?(char*)tparm(f,x1):(char*)0)
#endif
# define t_param_2(f,x1,x2) (f?(char*)tparm(f,x1,x2):(char*)0)
#else
  private char *tg_res;
# define t_goto(f,x1,x2)    (strcmp(tg_res=tgoto(f,x1,x2),"OOPS")?tg_res:(char*)0)
# define t_param_1(f,x1)    (f?(char*)t_goto(f,0,x1):(char*)0)
# define t_param_2(f,x1,x2) (f?(char*)t_goto(f,x2,x1):(char*)0)
#endif
/*}}}  */
/*{{{  outchar*/
private int outchar(int ch)
{
  if (scrout) oputc(ch);
  return(ch);
}
/*}}}  */
/*{{{  move_cursor_to*/
public void move_cursor_to(int y,int x)
{
  if (scrout)
     if (y==1 && x==1 && term_do_ho)
        tputs(term_do_ho,1,outchar);
     else if (term_do_cm)
      /*{{{  use term_do_cm*/
      { char const *s;

        if ((s=t_param_2(term_do_cm,y-1,x-1))==0)
           exit(r_init_err);
        tputs(s,1,outchar);
      }
      /*}}}  */
     else
      /*{{{  step by step*/
      { char const *s;

        tputs(term_do_ho,1,outchar);
        if (--y>0)
           if ((s=t_param_1(term_do_n_do,y)))
              tputs(s,1,outchar);
           else if (term_do_do)
              do tputs(term_do_do,1,outchar); while (--y);
           else
              exit(r_init_err);
        if (--x>0)
           if ((s=t_param_1(term_do_n_ri,x)))
              tputs(s,1,outchar);
           else if (term_do_ri)
              do tputs(term_do_ri,1,outchar); while (--x);
           else
              exit(r_init_err);
      }
      /*}}}  */
}
/*}}}  */
/*{{{  insLine:  if al known, do and return False, else do nothing and return True*/
public boolean insLine(int n)
{
  if (scrout)
   {
#if defined(USE_SCRBUFF) && !defined(BLOCK_FLUSH)
     char const *s;

     if (n>1 && (s=t_param_1(do_AL,n)))
        tputs(s,1,outchar);
     else
#endif
     if (al)
        while (n-->0)
           tputs(al,1,outchar);
     else
        return(True);
   }

  return(False);
}
/*}}}  */
/*{{{  DelLine:  if dl known, do and return False, else do nothing and return True*/
public boolean DelLine(int n)
{
  if (scrout)
   {
#if defined(USE_SCRBUFF) && !defined(BLOCK_FLUSH)
     char const *s;

     if (n>1 && (s=t_param_1(do_DL,n)))
        tputs(s,1,outchar);
     else
#endif
     if (dl)
        while (n-->0)
           tputs(dl,1,outchar);
     else
        return(True);
   }

  return(False);
}
/*}}}  */
#ifdef USE_SCRBUFF
# ifndef BLOCK_FLUSH
     /*{{{  step_or_move_cursor*/
     public void step_or_move_cursor(int y, int x, int dy, int dx)
     {
       if (dx || dy)
        { char const *s;

          if
           /*{{{  both diff or no valid capability given*/
           (    (dx&&dy)
             || (dx==1 && (s=term_do_ri)==0)
             || (dx>0 && (s=t_param_1(term_do_n_ri,dx))==0)
             || (dx==-1 && (s=term_do_le)==0)
             || (dx<0 && (s=t_param_1(term_do_LE,-dx))==0)
             || (dy==1 && (s=term_do_do)==0)
             || (dy>0 && (s=t_param_1(term_do_n_do,dy))==0)
             || (dy==-1 && (s=term_do_up)==0)
             || (dy<0 && (s=t_param_1(term_do_n_up,-dy))==0)
           )
           /*}}}  */
             move_cursor_to(y,x);
          else
             tputs(s,1,outchar);
        }
     }
     /*}}}  */
     /*{{{  insChar: return true, if not possible*/
     public void insChar(unsigned char *s,int l)
     {
       if (term_do_im || term_do_ic)
        { if (l)
           { if (term_do_im) tputs(term_do_im,1,outchar);
             do
              { if (term_do_ic) tputs(term_do_ic,1,outchar);
                oputc(*s);
                s++;
                if (term_do_ip) tputs(term_do_ip,1,outchar);
              }
             while (--l);
             if (term_do_ei) tputs(term_do_ei,1,outchar);
           }
        }
     }
     /*}}}  */
     /*{{{  delChar: return True, if not possible*/
     public void delChar(int n)
     {
       if (term_do_dc || term_do_n_dc)
        { char *s;

          if (n)
             if ((n>1 || !term_do_dc) && (s=t_param_1(term_do_n_dc,n)))
                tputs(s,1,outchar);
             else
                do
                   tputs(term_do_dc,1,outchar);
                while (--n);
        }
     }
     /*}}}  */
# endif
  /*{{{  cursor*/
  public void scr_cursor(boolean show)
  {
    char const *s;

    s=show?term_do_vs:term_do_vi;
    if (s)
       tputs(s,1,outchar);
  }
  /*}}}  */
#endif
/*{{{  moputc*/
#ifdef BLOCK_FLUSH
private
#else
public
#endif
        int moputc(int c,int n)
{
  if (scrout)
     if (n>0)
      { char *s;

        if ((n>M_RP_LIMIT) && (s=t_param_2(term_do_rp,(int)c,n)))
           tputs(s,1,outchar);
        else
        if
         (    !so_active
           && c==' '
           && n>M_DIR_RP_LIMIT
           && t_param_1(term_do_n_ic,n)
           && (s=t_param_1(term_do_n_dc,n))
         )
         { if (n<screen.w) tputs(s,1,outchar);
           tputs(t_param_1(term_do_n_ic,n),1,outchar);
           return(n);
         }
        else
           do
              oputc(c);
           while (--n);
      }

  return(0);
}
/*}}}  */
/*{{{  init_terminal*/
public void init_terminal(void)
{
  if (term_do_ti)
     tputs(term_do_ti,1,outchar);
  switch (kp_state)
   { case 'A':
     case 'a':
     default:
        if (term_do_ks)
         { tputs(term_do_ks,1,outchar);
           fflush(stdout);
         }
     case 'N':
     case 'n':
        break;
   }
}
/*}}}  */
/*{{{  reset_terminal*/
public void reset_terminal(void)
{
  move_cursor_to(screen.h,1);
  oputc('\n');
  switch (kp_state)
   { case 'A':
     case 'a':
     default:
        if (term_do_ke)
         { tputs(term_do_ke,1,outchar);
           fflush(stdout);
         }
     case 'N':
     case 'n':
        break;
   }
  if (term_do_te) tputs(term_do_te,1,outchar);
  fflush(stdout);
}
/*}}}  */
/*{{{  do_standout*/
public void do_standout(void)
{
  if (so)
   { so_wished=True;
     if (scrout)
      { so_active=True;
        tputs(so,1,outchar);
      }
   }
}
/*}}}  */
/*{{{  do_standend*/
public void do_standend(void)
{
  if (term_do_se)
   { so_wished=False;
     if (scrout)
      { so_active=False;
        tputs(term_do_se,1,outchar);
      }
   }
}
/*}}}  */
/*{{{  moveclreol*/
public void moveclreol(int y,int x)
{ if (scrout)
   { move_cursor_to(y,x);
     if (ce)
        tputs(ce,1,outchar);
     else
      { if (am && (y==screen.h)) x++;
        moputc(' ',screen.w-x);
      }
   }
}
/*}}}  */
/*{{{  clrscr*/
public void ClrScr(void)
{
  if (scrout)
     if (term_do_cl)
        tputs(term_do_cl,1,outchar);
     else if (term_do_ho && term_do_cd)
      /*{{{  home and clear to end of display*/
      { tputs(term_do_ho,1,outchar);
        tputs(term_do_cd,screen.h,outchar);
      }
      /*}}}  */
     else
      /*{{{  clear line by line*/
      { int i;

        for (i=1;i<=screen.h;moveclreol(i++,1));
        if (am) outchar(' ');
      }
      /*}}}  */
}
/*}}}  */
/*{{{  bell_audible*/
public void bell_audible(void)
{
  if (term_do_bl && scrout)
   { tputs(term_do_bl,1,outchar);
     fflush(scrout);
   }
}
/*}}}  */
/*{{{  bell_visible*/
public void bell_visible(void)
{
  if (term_do_vb && scrout)
   { tputs(term_do_vb,1,outchar);
     fflush(scrout);
   }
}
/*}}}  */
/*{{{  send_terminal*/
public void send_terminal(int c)
{
  putc(c,stdout);
}
/*}}}  */
/*{{{  flush_terminal*/
public void flush_terminal(void)
{
   fflush(stdout);
}
/*}}}  */
/*{{{  check_terminal*/
public boolean check_terminal(char const * const t)
{
  int l;
  boolean ok;

  ok=False;
  if (t[l=(strlen(t)-1)]=='*')
   { if (!strncmp(terminal,t,l-1)) ok=True; }
  else
   { if (!strcmp(terminal,t)) ok=True; }

  return(ok);
}
/*}}}  */
/*{{{  screen_off*/
public void screen_off(void)
{ scrout=0;
}
/*}}}  */
/*{{{  screen_on*/
public void screen_on(void)
{
  scrout=stdout;
  if (so_wished!=so_active) if (so_wished) do_standout(); else do_standend();
}
/*}}}  */
/*{{{  get_terminal_capability*/
#if (defined(MOUSY) && defined(XTERM))
   /*{{{  xterm_size*/
   private boolean xterm_size(char const * const t)
   {
     if (!strcmp(t,XTERM_MOUSE_NAME) && isatty(fileno(stdout)))
      /*{{{  try to get from xterm*/
        for (;;)
         { char b[32];
           char *d,*s;

           /*{{{  activate the sending*/
           fputs(XTERM_GET_SIZE,stdout);
           fflush(stdout);
           /*}}}  */
           /*{{{  read the header*/
           s=XTERM_SIZE_HEAD;
           while (*s && (read_err(kbd_file,b),b[0]==*s)) s++;
           if (*s) break;
           /*}}}  */
           /*{{{  get lines*/
           d=b;
           while (isdigit((read_err(kbd_file,d),*d++)));
           if (*--d!=XTERM_SIZE_MID[0]) break;
           *d=0;
           screen.h=atoi(b);
           /*}}}  */
           /*{{{  get rest of mid text*/
           s=XTERM_SIZE_MID+1;
           while (*s && (read_err(kbd_file,b),b[0]==*s)) s++;
           /*}}}  */
           /*{{{  get columns*/
           d=b;
           while (isdigit((read_err(kbd_file,d),*d++)));
           if (*--d!=XTERM_SIZE_END[0]) break;
           *d=0;
           screen.w=atoi(b);
           /*}}}  */
           /*{{{  get rest of end text*/
           s=XTERM_SIZE_END+1;
           while (*s && (read_err(kbd_file,b),b[0]==*s)) s++;
           /*}}}  */
           return(False);
         }
      /*}}}  */
     return(True);
   }
   /*}}}  */
#   define no_xterm_size xterm_size(terminal)
#else
#   define no_xterm_size 1
#endif

public int get_terminal_capability(void)
{
  /*{{{  terminal data variables*/
#  ifdef TIOCGWINSZ
     int res;
     struct winsize winsz;
#  endif
#  ifdef TERMINFO
     int result;
#    define GET_TERM_STR(cap,info) info
#    define GET_TERM_VAL(cap,info) info
#    define GET_TERM_TAG(cap,info) info
#    define GET_DATABASE(term) (setupterm(term,2,&result),resetterm(),result)
#    define INIT_DATABASE()
#  else
     static char termcap_area[512];
     static char bp[1024];
     static char *pointer;
#    define GET_TERM_STR(cap,info) tgetstr(cap,&pointer)
#    define GET_TERM_VAL(cap,info) tgetnum(cap)
#    define GET_TERM_TAG(cap,info) tgetflag(cap)
#    define GET_DATABASE(term) tgetent(bp,term)
#    define INIT_DATABASE() pointer=termcap_area
#  endif
  /*}}}  */

  if (!terminal)
   /*{{{  first call => get terminal dependent strings*/
   {
     /*{{{  get terminal name*/
     if (!(terminal=getenv(TERM)))
#       ifdef DEFAULT_TERM
           terminal=DEFAULT_TERM;
#       else
         { oputs((char *)get_msg(M_NO_TERM));
           return(1);
         }
#       endif
     /*}}}  */
     /*{{{  read termcap data*/
     switch (GET_DATABASE(terminal))
      { case -1: { oputs((char *)get_msg(F_NO_TERMCAP,TERM_DATA_BASE));return (1); }
        case  0: { oputs((char *)get_msg(F_TERMB,terminal,TERM_DATA_BASE));return (1); }
      }
     INIT_DATABASE();
     /*}}}  */
     /*{{{  get needed features*/
     /*{{{  cursor motion*/
     /*{{{  cursor move*/
     term_do_cm=GET_TERM_STR("cm",cursor_address);
     /*}}}  */
     /*{{{  home*/
     term_do_ho=GET_TERM_STR("ho",cursor_home);
     /*}}}  */
     /*{{{  down*/
     term_do_do=GET_TERM_STR("do",cursor_down);
     /*}}}  */
     /*{{{  down**/
     term_do_n_do=GET_TERM_STR("DO",parm_down_cursor);
     /*}}}  */
     /*{{{  right*/
     term_do_ri=GET_TERM_STR("nd",cursor_right);
     /*}}}  */
     /*{{{  right**/
     term_do_n_ri=GET_TERM_STR("RI",parm_right_cursor);
     /*}}}  */
     if (!term_do_cm && !(term_do_ho && (term_do_do || term_do_n_do) && (term_do_ri || term_do_n_ri)))
      { oputs((char *)get_msg(F_TERMA,"cursor motion (cm)",terminal));
        exit(r_init_err);
      }
     /*}}}  */
     /*{{{  screen size*/
#     ifdef TIOCGWINSZ
        if (ioctl(0,TIOCGWINSZ,&winsz)!=-1)
         { screen.h=winsz.ws_row;
           screen.w=winsz.ws_col;
         }
#     endif
     if (screen.h<=0)
        screen.h=GET_TERM_VAL("li",lines);
     if (screen.w<=0)
        screen.w=GET_TERM_VAL("co",columns);
     if (screen.h<=0 || screen.w<=0)
      { oputs((char *)get_msg(F_TERMA,STR_WINSIZE,terminal));
        exit(r_init_err);
      }
     /*}}}  */
     /*}}}  */
     /*{{{  get optional features*/
     /*{{{  init/reset terminal*/
     term_do_ti=GET_TERM_STR("ti",enter_ca_mode);
     term_do_te=GET_TERM_STR("te",exit_ca_mode);
     /*}}}  */
     /*{{{  clear's*/
     ce=GET_TERM_STR("ce",clr_eol);
     term_do_cl=GET_TERM_STR("cl",clear_screen);
     term_do_cd=GET_TERM_STR("cd",clr_eos);
     /*}}}  */
     /*{{{  add/delete line*/
     al=GET_TERM_STR("al",insert_line);
     do_AL=GET_TERM_STR("AL",parm_insert_line);
     dl=GET_TERM_STR("dl",delete_line);
     do_DL=GET_TERM_STR("DL",parm_delete_line);
     /*}}}  */
     /*{{{  bells*/
     term_do_bl=GET_TERM_STR("bl",bell);
     term_do_vb=GET_TERM_STR("vb",flash_screen);
     /*}}}  */
     /*{{{  keypad*/
     term_do_ks=GET_TERM_STR("ks",keypad_xmit);
     term_do_ke=GET_TERM_STR("ke",keypad_local);
     /*}}}  */
     /*{{{  standout*/
     /*{{{  get standout glitches*/
     if ((sg=GET_TERM_VAL("sg",magic_cookie_glitch))==-1)
        sg=0;
     xs=GET_TERM_TAG("xs",ceol_standout_glitch);
     xt=GET_TERM_TAG("xt",teleray_glitch);
#     ifdef USE_SCRBUFF
#       ifndef BLOCK_FLUSH
           mi=GET_TERM_TAG("mi",move_insert_mode);
#       endif
#     endif
     /*}}}  */
     if
      (
#     ifndef USE_SCRBUFF
              xt || xs ||
#     endif
        (    (    !(so=GET_TERM_STR("so",enter_standout_mode))
               || !(term_do_se=GET_TERM_STR("se",exit_standout_mode))
             )
          && (    !(so=GET_TERM_STR("mr",enter_reverse_mode))
               || !(term_do_se=GET_TERM_STR("me",exit_attribute_mode))
             )
          && (    !(so=GET_TERM_STR("us",enter_underline_mode))
               || !(term_do_se=GET_TERM_STR("ue",exit_underline_mode))
             )
        )
      )
        so=term_do_se=0;
     /*}}}  */
     /*{{{  repeat character*/
     term_do_rp=GET_TERM_STR("rp",repeat_char);
     /*}}}  */
     /*{{{  cursor*/
#     ifdef USE_SCRBUFF
        term_do_vi=GET_TERM_STR("vi",cursor_invisible);
        term_do_vs=GET_TERM_STR("vs",cursor_visible);
        if (term_do_vi && !term_do_vs)
           term_do_vs=GET_TERM_STR("ve",cursor_normal);
        else if (!term_do_vi && term_do_vs)
           term_do_vi=GET_TERM_STR("ve",cursor_normal);
        if (!term_do_vs)
           term_do_vi=0;
        else if (!term_do_vi)
           term_do_vs=0;
#     endif
     /*}}}  */
#     if defined(USE_SCRBUFF) && !defined(BLOCK_FLUSH)
      /*{{{  additional cursor movements*/
        term_do_up=GET_TERM_STR("up",cursor_up);
        term_do_n_up=GET_TERM_STR("UP",parm_up_cursor);
        term_do_le=GET_TERM_STR("le",cursor_left);
        term_do_LE=GET_TERM_STR("LE",parm_left_cursor);
      /*}}}  */
#     endif
     if (!GET_TERM_TAG("in",insert_null_glitch))
      /*{{{  read insert/delete character stuff*/
      { term_do_n_ic=GET_TERM_STR("IC",parm_ich);
        term_do_n_dc=GET_TERM_STR("DC",parm_dch);
#       if defined(USE_SCRBUFF) && !defined(BLOCK_FLUSH)
           term_do_im=GET_TERM_STR("im",enter_insert_mode);
           term_do_ic=GET_TERM_STR("ic",insert_character);
           term_do_ip=GET_TERM_STR("ip",insert_padding);
           term_do_ei=GET_TERM_STR("ei",exit_insert_mode);
           term_do_dc=GET_TERM_STR("dc",delete_character);
#       endif
      }
      /*}}}  */
     /*}}}  */
     /*{{{  check some strange features*/
#     if defined(USE_SCRBUFF) && !defined(BLOCK_FLUSH)
        use_shift=False;
        if ((term_do_im || term_do_ic) && (term_do_dc || term_do_n_dc) && !xs)
           use_shift=True;
#     endif
     am=GET_TERM_TAG("am",auto_right_margin);
     hz=GET_TERM_TAG("hz",tilde_glitch);
     /*}}}  */
   }
   /*}}}  */
  else
   /*{{{  get window-size*/
   { if (no_xterm_size)
      {
#       ifdef TIOCGWINSZ
           res=ioctl(0,TIOCGWINSZ,&winsz);
           screen.h=(res==-1 || winsz.ws_row<=0)
                     ? GET_TERM_VAL("li",lines)
                     : winsz.ws_row;
           screen.w=(res==-1 || winsz.ws_col<=0)
                      ? GET_TERM_VAL("co",columns)
                      : winsz.ws_col;
#       else
           screen.h=GET_TERM_VAL("li",lines);
           screen.w=GET_TERM_VAL("co",columns);
#       endif
      }
   }
   /*}}}  */
  /*{{{  minimum window is 1x1*/
  if (screen.h<=0)
     screen.h=1;
  if (screen.w<=0)
     screen.w=1;
  /*}}}  */
  return (0);
}
/*}}}  */
