// ---------------------------------------------------------------------------
// - ctrm.cxx                                                                -
// - standard platform library : c terminal function implementation          -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2011 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "csio.hpp"
#include "cstr.hpp"
#include "ccnv.hpp"
#include "ctrm.hpp"
#include "ctrm.hxx" 

#ifdef AFNIX_HAVE_NONCONST_TIGET
namespace afnix {
  // get capability flag
  static bool c_tigetflag (const char* capname) {
    char* buf = const_cast <char*> (capname);
    int   val = tigetflag (buf);
    return (val <= 0) ? false : true;
  }
  // get capability string
  static char* c_tigetstr (const char* capname) {
    char* buf = const_cast <char*> (capname);
    char* ptr = tigetstr (buf);
    return (ptr == (char*)-1) ? nilp : ptr;
  }
}
#else
namespace afnix {
  // get capability flag
  static bool c_tigetflag (const char* capname) {
    int val = tigetflag (capname);
    return (val <= 0) ? false : true;
  }
  // get capability string
  static char* c_tigetstr (const char* capname) {
    char* ptr = tigetstr (capname);
    return (ptr == (char*)-1) ? nilp : ptr;
  }
}
#endif

#ifdef AFNIX_HAVE_COMPLEX_TPARM
namespace afnix {
  static char* c_tparm_0 (char* data) {
    return tparm (data,0,0,0,0,0,0,0,0,0);
  }
  static char* c_tparm_1 (char* data, long p0) {
    return tparm (data,p0,0,0,0,0,0,0,0,0);
  }
}
#else
namespace afnix {
  static char* c_tparm_0 (char* data) {
    return tparm (data);
  }
  static char* c_tparm_1 (char* data, long p0) {
    return tparm (data, p0);
  }
}
#endif

#ifdef AFNIX_DELETE_ISBS
namespace afnix {
  // change the backspace setting
  static void set_delete_isbs (char** tinfo) {
    tinfo[ITERM_BACKSPACE] = c_strmak (delc);
  }
}
#else
namespace afnix {
  // change the backspace setting
  static void set_delete_isbs (char** tinfo) {
    if (tinfo[ITERM_BACKSPACE] == nilp) 
      tinfo[ITERM_BACKSPACE] = c_strmak (bspc);
  }
}
#endif

namespace afnix {
  // check if the stream id is a terminal

  bool c_istty (const int sid) {
    return (isatty (sid) == 1) ? true : false;
  }

  // get the terminal attributes

  void* c_gtattr (const int sid) {
    struct termios* tattr = new struct termios;

    // check for terminal first
    if (c_istty (sid) == false) return nilp;
    // get the terminal attributes
    if (tcgetattr (sid, tattr) != 0) return nilp;
    return tattr;
  }

  // set the terminal attributes

  void c_stattr (const int sid, void* tattr) {
    // check for structure and tty
    if (tattr == nilp) return;
    if (c_istty (sid) == false) return;
 
    // set terminal attributes
    tcsetattr (sid, TCSANOW, (struct termios*) tattr);
  }

  // reset terminal mode - put in non canonical mode

  bool c_stcanon (const int sid) {
    // check for terminal first
    if (c_istty (sid) == false) return false;

    // reset canonical mode and echo
    struct termios tattr;

    tcgetattr (STDIN_FILENO, &tattr);
    tattr.c_lflag    &= ~(ICANON|ECHO);
    tattr.c_cc[VMIN]  = 1;
    tattr.c_cc[VTIME] = 0;
    if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr) != 0) return false;
    return true;
  }

  // this function checks if a string is bounded in the tinfo array
  static bool check_tinfo (char** tinfo, long size, const char* name) {
    for (long i = 0; i < size; i++) 
      if (c_strcmp (tinfo[i], name) == true) return true;
    return false;
  }

  // these functions fix some inconsistencies in the terminfo/termcap world
  // this is only coming from various experiments
  static char** fix_tinfo_input (char** tinfo) {
    // fix delete character
    set_delete_isbs (tinfo);

    // fix arrows and delete
    if (check_tinfo (tinfo, ITERM_PARMS_MAX, XTERM_ARROW_UP) == false) 
      tinfo[ITERM_STD_UP] = c_strdup (XTERM_ARROW_UP);
    if (check_tinfo (tinfo, ITERM_PARMS_MAX, XTERM_ARROW_DOWN) == false) 
      tinfo[ITERM_STD_DOWN] = c_strdup (XTERM_ARROW_DOWN);
    if (check_tinfo (tinfo, ITERM_PARMS_MAX, XTERM_ARROW_RIGHT) == false) 
      tinfo[ITERM_STD_RIGHT] = c_strdup (XTERM_ARROW_RIGHT);
    if (check_tinfo (tinfo, ITERM_PARMS_MAX, XTERM_ARROW_LEFT) == false) 
      tinfo[ITERM_STD_LEFT ] = c_strdup (XTERM_ARROW_LEFT);
    if (check_tinfo (tinfo, ITERM_PARMS_MAX, XTERM_DELETE_KEY) == false) 
      tinfo[ITERM_STD_DELETE] = c_strdup (XTERM_DELETE_KEY);
    if (check_tinfo (tinfo, ITERM_PARMS_MAX, XTERM_INSERT_KEY) == false) 
      tinfo[ITERM_STD_INSERT] = c_strdup (XTERM_INSERT_KEY);

    // and hopefully, it is working
    return tinfo;
  }

  static char** fix_tinfo_output (char** tinfo) {
    // check for cub1 - if not set we use arrow left
    if (c_strlen (tinfo[OTERM_MOVE_LEFT]) == 0)
      tinfo[OTERM_MOVE_LEFT] = c_strdup (XTERM_ARROW_LEFT);
    // check for cuf1 - if not set we use the standard right arrow
    if (c_strlen (tinfo[OTERM_MOVE_RIGHT]) == 0)
      tinfo[OTERM_MOVE_RIGHT] = c_strdup (XTERM_ARROW_RIGHT);
    // check for cuu1 - if not set we use the standard up arrow
    if (c_strlen (tinfo[OTERM_MOVE_UP]) == 0)
      tinfo[OTERM_MOVE_UP] = c_strdup (XTERM_ARROW_UP);
    // check for cud1 - if not set we use the standard up arrow
    if (c_strlen (tinfo[OTERM_MOVE_DOWN]) == 0)
      tinfo[OTERM_MOVE_DOWN] = c_strdup (XTERM_ARROW_DOWN);
    // check that we can fix background colors
    if (c_strlen (tinfo[OTERM_RESET_COLOR]) == 0) {
      delete [] tinfo[OTERM_SETFG_MODE];
      delete [] tinfo[OTERM_RESET_COLOR];
      tinfo[OTERM_SETFG_MODE]  = nilp;
      tinfo[OTERM_RESET_COLOR] = nilp;
    }
    // hopefully, we are set
    return tinfo;
  }
  
  // return an array of terminal capabilities

  char** c_tinfo (bool imode) {
    int status = 0;
    // read terminfo database
    if (setupterm (getenv ("TERM"),STDOUT_FILENO,&status) != OK) return nilp;

    // create an array of capabilities and initialize it
    long len = imode ? ITERM_PARMS_MAX : OTERM_PARMS_MAX;
    char** result = new char*[len];
    for (long i = 0; i < len; i++) result[i] = nilp;

    // query input capabilities
    if (imode == true) {
      result[ITERM_BACKSPACE]    = c_strdup (c_tigetstr ("kbs"));
      result[ITERM_DELETE]       = c_strdup (c_tigetstr ("kdch1"));
      result[ITERM_ARROW_UP]     = c_strdup (c_tigetstr ("kcuu1"));
      result[ITERM_ARROW_DOWN]   = c_strdup (c_tigetstr ("kcud1"));
      result[ITERM_ARROW_LEFT]   = c_strdup (c_tigetstr ("kcub1"));
      result[ITERM_ARROW_RIGHT]  = c_strdup (c_tigetstr ("kcuf1"));
      result[ITERM_INSERT_KEY]   = c_strdup (c_tigetstr ("kich1"));
      result[ITERM_STD_UP]       = nilp;
      result[ITERM_STD_DOWN]     = nilp;
      result[ITERM_STD_RIGHT]    = nilp;
      result[ITERM_STD_LEFT]     = nilp;
      result[ITERM_STD_DELETE]   = nilp;
      result[ITERM_STD_INSERT]   = nilp;
    } else {
      result[OTERM_DELETE_CHAR]  = c_strdup (c_tigetstr ("dch1"));
      result[OTERM_MOVE_LEFT]    = c_strdup (c_tigetstr ("cub1"));
      result[OTERM_MOVE_RIGHT]   = c_strdup (c_tigetstr ("cuf1"));
      result[OTERM_MOVE_UP]      = c_strdup (c_tigetstr ("cuu1"));
      result[OTERM_MOVE_DOWN]    = c_strdup (c_tigetstr ("cud1"));
      result[OTERM_MOVE_BOL]     = c_strdup (c_tigetstr ("cr"));
      result[OTERM_INSERT_CHAR]  = c_strdup (c_tigetstr ("ich1"));
      result[OTERM_IMODE_START]  = c_strdup (c_tigetstr ("smir"));
      result[OTERM_IMODE_END]    = c_strdup (c_tigetstr ("rmir"));
      result[OTERM_SETFG_MODE]   = c_strdup (c_tigetstr ("setaf"));
      result[OTERM_RESET_COLOR]  = c_strdup (c_tigetstr ("oc"));
      result[OTERM_CLEAR_SCREEN] = c_strdup (c_tigetstr ("clear"));

      // local fix for color
      if (c_strlen (result[OTERM_RESET_COLOR]) == 0)
	result[OTERM_RESET_COLOR] = c_strdup (c_tigetstr ("op"));
    }
    // here is our array
    return imode ? fix_tinfo_input (result) : fix_tinfo_output (result);
  }

  // return an array of terminal capabilities flag

  bool* c_tbool (void) {
    int status = 0;
    // read terminfo database
    if (setupterm (getenv ("TERM"),STDOUT_FILENO,&status) != OK) return nilp;
    
    // create an array of capabilities and initialize it
    bool* result = new bool [BTERM_PARMS_MAX];

    // query capabilities
    result[BTERM_AUTO_WRAP] = c_tigetflag ("am") && c_tigetflag ("xn");
    // the array of capabilities
    return result;
  }

  // return true if a parameter is valid

  bool c_tpvld (char** tinfo, const long index, const bool mode) {
    // check terminal info and index
    if ((tinfo == nilp) || (index < 0)) return false;
    // check bound based on mode
    if ((mode == true)  && (index >= OTERM_PARMS_MAX)) return false;
    if ((mode == false) && (index >= ITERM_PARMS_MAX)) return false;
    // check parameter
    if (c_strlen (tinfo[index]) == 0) return false;
    return true;
  }

  // send a character to the standard output

  void c_tparm (const int sid, char** tinfo, const long index) {
    if ((tinfo == nilp) || (index < 0) || (index >= OTERM_PARMS_MAX)) return;
    char* data = tinfo[index];
    if (data == nilp) return;
    char* buffer = c_tparm_0 (data);
    long  length = c_strlen (buffer);
    c_write (sid,buffer,length);
  }

  // turn on/off the terminal error mode

  void c_temode (const int sid, char** tinfo, const bool mode) {
    if (tinfo == nilp) return;
    char* setaf = tinfo[OTERM_SETFG_MODE];
    char* rstaf = tinfo[OTERM_RESET_COLOR];
    if ((setaf == nilp) || (rstaf == nilp)) return;
    char* buffer = mode ? c_tparm_1 (setaf,1) : c_tparm_0 (rstaf);
    long  length = c_strlen (buffer);
    c_write (sid,buffer,length);
  }
}

#ifdef AFNIX_HAVE_WINSIZE
namespace afnix {
  // get the number of columns for a tty 
  long c_getcols (const int sid) {
    // try ioctl first
    if (c_istty (sid) == true) {
      struct winsize size;
      if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &size) == 0) {
        long cols = (long) size.ws_col;
        if (cols != 0) return cols;
      }
    }
    // may be the environment will help us !!!
    bool status = true;
    long  cols  = c_atoll (getenv ("COLUMNS"), status);
    if ((status == true) && (cols != 0)) return cols;
    // nothing match so we return 0
    return 0;
  }
}
#endif

#ifdef AFNIX_HAVE_DELTERM
namespace afnix {
  // free the terminal attributes structure
  void c_ftattr (void* ptr) {
    struct termios* tattr = (struct termios*) ptr;
    delete tattr;
    del_curterm (cur_term);
  }
}
#else
namespace afnix {
  // free the terminal attributes structure
  void c_ftattr (void* ptr) {
    struct termios* tattr = (struct termios*) ptr;
    delete tattr;
  }
}
#endif
