/*
 * front/cursio.c
 *   Generic curses IO interface
 *   TODO: use curses ;-)
 *   TODO: so be portable
 * 
 * $Id: cursio.c,v 1.1.1.1 2002/01/24 20:19:48 sembera Exp $
 */
/*
 * IIRC - IIRC Is a Real Client (Modular IRC Client)
 * Copyright (C) 2001  Jan Sembera <fis@ji.cz>
 * Copyright (C) 2001  Petr Baudis <pasky@ji.cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

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

#ifndef NO_LINUX_DEP
#include <sys/ioctl.h>
#endif

#include "cursio.h"

static int lines, columns;

int
io_init() {
  char *envval;
#ifndef NO_LINUX_DEP
  struct winsize window;

  if (ioctl(0, TIOCGWINSZ, &window) < 0) {
    lines = 25;
    columns = 80;
  } else {
    lines = window.ws_row; if (! lines) lines = 25;
    columns = window.ws_col; if (! columns) columns = 80;
  }
#else
  
  /* FIXME: not working actually */
  envval = getenv("LINES");
  if (! envval)
    lines = 25;
  else
    lines = atoi(envval);

  envval = getenv("COLUMNS");
  if (! envval)
    columns = 80;
  else
    columns = atoi(envval);
#endif
  
  return 1;
}

int
io_done() {
  return 1;
}

int io_maxx() { return columns; }
int io_maxy() { return lines; }

/*
 * in io_move_, io_cls_ and io_color_, the result will be APPENDED to str!
 */
int
io_move_(char *str, int y, int x) {
  sprintf(str, "%s[%d;%dH", str, y, x); /* ANSI CUP */
  
  return 1;
}

int
io_cls_(char *str) {
  sprintf(str, "%s[2J", str); /* ANSI ED */
  
  return 1;
}

int
io_cle_(char *str) {
  sprintf(str, "%s[K", str); /* ANSI EL */
  
  return 1;
}

/*
 * Yes, this all is evil and unportable! But it is ANSI ;-)
 * 
 * -1 as fg or bg means do not change it (w/o anything just restore defaults)
 * Return # of chars printed (use io_color for normal operation)
 * Well, it doesn't ;-)
 *
 * This is propably most readable function i ever wrote, so please
 * don't mess with it too much :-)
 *
 * IMHO we are doing this a lot more intelligent than in those damn'd BitchX
 * (and propably EPIC also) :-) - boys, keep this simple ;-)
 */
int
io_color_(char *str, int fg, int bg, int bold, int inverse) {
  
  /* Please, when anyone will be able to explain me by what system are those
   * numbers (IRC ones) assigned, do so. :|
   *
   * Well, i manually tried everything and wrote down emulation for it ;-)
   * But i still don't get the mean of this all :-))... We'll see later maybe.
   *
   * (pht told me it is from Khaled, if (so) let's seek'n'destroy Khaled ;-)
   *
   * (note: doc/color.txt along BX is completely out of the reality - well,
   *  it isn't, but it describes only ANSI standart, not ^C one)
   *
   * So, the color number is passed through following:
   *
   * - If number is <20, get the SGR value from tr[]
   * - If number is >29 and <40 for foreground, or >39 and <50 for background,
   *   pass it as SGR value
   * - If number is >49, subscribe 20 (resp. 10), pass it as SGR value and set
   *   bold (fg) or blink (bg)
   * - Otherwise take only the first digit of the number, rest keep there:
   *   num[0] == '0' -> num[1] == '0'-'9'
   *   num[0] == '1' -> num[1] == '0'-'6'
   *   num[0] == '3' -> num[1] == '0'-'7'
   *   num[0] == '4' -> num[1] == '0'-'7'
   *   num[0] == '5' -> num[1] == '0'-'8' (8 == 0 ?)
   *
   * There's anyway at least one known behaviour NOT same as in BitchX:
   * if 40 <= fg <= 47, result is not black, but default! (gray)
   * (which is imho more intelligent)
   * 
   * Unfortunately, part of this dirty parsing has to be done in _cprintf()
   * (resp. the last step must be)... :-(
   */
  
  /* Convert IRC -> ANSI(SGR) */
  /* + 30/40, | 8 == bold (stripped if bg) */
  int tr[] = {
    15, /*  0 White           */
    0,  /*  1 Black           */
    4,  /*  2 Blue (Midnight) */
    2,  /*  3 Green (Ooze)    */
    1,  /*  4 Red (Rust)      */
    3,  /*  5 Brown           */
    5,  /*  6 Magenta (Evil)  */
    9,  /*  7 Red (Blood)     */
    11, /*  8 Yellow          */
    10, /*  9 Green (Grass)   */
    6,  /* 10 Cyan (Water)    */
    14, /* 11 Cyan (Sky)      */
    12, /* 12 Blue (Evening)  */
    13, /* 13 Magenta (Good)  */
    8,  /* 14 Gray (Dark)     */
    7,  /* 15 Gray (System)   */
    15, /* 16 White           */
    1, 
    1,
    1,
  };
  
  int blink = 0,
      use_fg = 0,
      use_bg = 0;
  char buf[16];
  
  /* Analyze color values */
  
  if ((fg >= 20 && fg <= 29) || (fg >= 38 && fg <= 49) || (fg >= 59) || (fg < -1))
    fg = -1;
  if ((bg >= 20 && bg <= 39) || (bg >= 48 && bg <= 49) || (bg >= 59) || (bg < -1))
    bg = -1;
  
  if (fg > -1)
    use_fg = 1;
  if (bg > -1)
    use_bg = 1;

  /* Convert into 'real' ANSI values */

  if (use_fg && (fg < 20)) {
    fg = tr[fg];
    if (fg & 8) { /* move bit 8 to bit 64 */
      fg ^= 8;
      fg |= 64;
    }
  }
  if (use_bg && (bg < 20))
    bg = tr[bg] & 7;

  if (fg == 58)
    fg = 50;
  if (bg == 58)
    bg = 50;
  
  if (fg > 49)
    fg = (fg - 20) | 64;
  if (bg > 49)
    bg = (bg - 10) | 64;
  
  if (use_fg && (fg & 64)) {
    bold = 1;
    fg &= 63;
  }
  if (use_bg && (bg & 64)) {
    blink = 1;
    bg &= 63;
  }
  
  /* Compose control sequences */
  
  if (! use_fg && ! use_bg && ! bold && ! inverse && ! blink)
    strcat(str, "[0m");
  else {
    if (use_fg) {
      sprintf(buf, "[%dm", fg);
      strcat(str, buf);
    }

    if (use_bg) {
      sprintf(buf, "[%dm", bg);
      strcat(str, buf);
    }

    if (bold)
      strcat(str, "[1m");

    if (inverse)
      strcat(str, "[7m");

    if (blink)
      strcat(str, "[5m");
  }
  
  return 1;
}

/* load a color from file (export?)
 *
 * see header of io_color_ for further details why it is
 * parsed in so strange and silly way
 */
void
load_color(int is_fg, int *col, int *index, char *buf) {
  int col2;
  
  if (isdigit(buf[*index])) {
    *col = buf[*index] - '0';
    (*index)++;

    if (isdigit(buf[*index])) {
      col2 = buf[*index] - '0';
      
      switch (*col) {
	case 0: *col = col2; /* ignore leading zero */
		(*index)++; /* don't stick here */
		break;
		
	case 1: if (col2 <= 6) /* 10 - 16 range */
		  *col = 10 + col2;
		(*index)++;
		break;

	case 3: if (! is_fg) /* only foreground */
		  break;
		if (col2 <= 7) /* ANSI SGR code */
		  *col = 30 + col2;
		(*index)++;
		break;

	case 4: if (is_fg) /* only background */
		  break;
		if (col2 <= 7) /* ANSI SGR code */
		  *col = 40 + col2;
		(*index)++;
		break;

	case 5: if (col2 <= 8) /* ANSI SGR code emphasized */
		  *col = 50 + col2;
		(*index)++;
		break;
      }
    }
  }
}

int
_csprintf(char *dest, char *fmt, ...) {
  va_list vl;
  char buf[10240];
  char nbuf[20480];
  char *jbuf;
  int index;
  int fg = -1,
      bg = -1,
      bold = 0,
      inverse = 0;

  /* Print it to buffer */
  
  va_start(vl, fmt);
  vsprintf(buf, fmt, vl);
  va_end(vl);

  /* Strip ^B, ^C and ^V, instead of them put there control sequences */

  nbuf[0] = 0;
  strcat(buf, "|"); /* for \3 cases, to use strtol() happily */
  for (index = 0; index < strlen(buf); index++) {
    switch (buf[index]) {
      case '\2':  if (bold) bold = 0; else bold = 1;
	          io_color_(nbuf, fg, bg, bold, inverse);
		  break;
		  
      case '\26': if (inverse) inverse = 0; else inverse = 1;
		  io_color_(nbuf, fg, bg, bold, inverse);
		  break;
		  
      case '\3':  index++;
		  fg = bg = -1;
		  
		  load_color(1, &fg, &index, buf);
		  
		  if (buf[index] == ',') {
		    index++;

		    load_color(0, &bg, &index, buf);
		  }

		  index--; /* has to point *before* next char */

		  io_color_(nbuf, fg, bg, bold, inverse);
		  break;
		  
      default:    jbuf = nbuf + strlen(nbuf);
		  jbuf[0] = buf[index];
		  jbuf[1] = 0;
		  break;
    }
  }
  nbuf[strlen(nbuf) - 1] = 0; /* drop the last character ('|') */
  io_color_(nbuf, -1, -1, 0, 0); /* restore defaults */

  /* Final strcpy()! */

  strcpy(dest, nbuf);

  return 1;
}

int
_cprintf(char *fmt, ...) {
  va_list vl;
  char buf[10240];
  char nbuf[20480];
  int ret;

  /* Print it to buffer */
  
  va_start(vl, fmt);
  vsprintf(buf, fmt, vl);
  va_end(vl);

  /* Give it to csprintf() */

  ret = _csprintf(nbuf, "%s", buf);

  /* And pop it out */

  fputs(nbuf, stdout);

  return ret;
}

/*
 * Nope, I don't like this, but it is neccessary to proper splitting of lines.
 * It now recognizes only SGR escape sequence!
 */
int
io_ansi_strlen(char *str) {
  int col, realcol;
  int escape = 0;

  for (col = 0, realcol = 0; col<strlen(str); ) {
    switch (str[col]) {
      case '': escape = 1;
		 break;
      case 'm':  if (escape) escape = 0;
      default:   if (!escape) realcol++;
    }
  }

  return realcol;
}
