/* This file is part of the Project Athena Zephyr Notification System.
 * It is one of the source files comprising zwgc, the Zephyr WindowGram
 * client.
 *
 *      Created by:     Marc Horowitz <marc@athena.mit.edu>
 *
 *      $Id: 7e4924a34c42feb9dddcadecf0f4c8068f4de994 $
 *
 *      Copyright (c) 1989 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h".
 */

#include <sysdep.h>

#if (!defined(lint) && !defined(SABER))
static const char rcsid_tty_filter_c[] = "$Id: 7e4924a34c42feb9dddcadecf0f4c8068f4de994 $";
#endif

#include <zephyr/mit-copyright.h>

/****************************************************************************/
/*                                                                          */
/*                         The tty & plain filters:                         */
/*                                                                          */
/****************************************************************************/
#ifdef HAVE_TERMCAP_H
#include <termcap.h>
#else
#ifdef HAVE_TERM_H
#ifdef HAVE_TERMIO_H
/* I blame Solaris.  Solaris to blame. */
#include <termio.h>
#endif
#ifdef HAVE_CURSES_H
#include <curses.h>
#endif
#include <term.h>
#endif
#endif

#include <zephyr/zephyr.h>

#include "new_memory.h"
#include "new_string.h"
#include "variables.h"
#include "string_dictionary_aux.h"
#include "formatter.h"
#include "zwgc.h"
#include "error.h"
#include "tty_filter.h"

/***************************************************************************/
#ifndef HAVE_TERMCAP_H
extern int tgetent();
extern char *tgetstr(),*getenv();
#ifdef linux
extern speed_t ospeed;
#else
extern short ospeed;
#endif
char PC;
#endif

/* Dictionary naming convention:

   B.xxx is the termcap sequence to begin environment xxx.
   E.xxx is the termcap sequence to end environment xxx.

   */

static string_dictionary termcap_dict;
static char code_buf[10240], *code_buf_pos = code_buf, *code;

/* Define the following commands:

   (Hopefully) shared with all devices:
   
   @center	Guess.
   
   @em		Emphasis.  User underline if available, else reverse video.
   @bold	Bold letters.  If not available, reverse video, else underline.
   @beep	"bl" termcap entry, else "^G"

   Other:

   @blink	"mb"/"me" termcap entry, else nothing.
   @rv		"so"/"se" termcap entry.
   @u		"us"/"ue" termcap entry.
 */

#define TD_SET(k,v) (string_dictionary_Define(termcap_dict, (k), &ex)->value \
		     = (v))
#define EXPAND(k) (code = code_buf_pos, tputs(tmp, 1, tty_outc), \
		   *code_buf_pos++ = 0, TD_SET(k, code))

static int
tty_outc(int c)
{
    *code_buf_pos++ = c;
    return 0;
}

/* ARGSUSED */
int
tty_filter_init(char *drivername,
		char notfirst,
		int *pargc,
		char **argv)
{
    static char st_buf[128];
    char tc_buf[1024], *p = st_buf, *tmp, *term;
    int ex;
    string_dictionary_binding *b;
    int isrealtty = string_Eq(drivername, "tty");
    struct termios tbuf;

    ospeed = (tcgetattr(STDIN_FILENO, &tbuf) == 0) ? cfgetospeed(&tbuf) : 2400;

    if (termcap_dict == (string_dictionary) NULL)
      termcap_dict = string_dictionary_Create(7);

    if (!(term = getenv("TERM"))) {	/* Only use termcap if $TERM.	*/
	if (isrealtty && !notfirst)
	    /* only complain if initializing tty mode, and would be first
	       available port */
	    ERROR("$TERM not set.  tty mode will be plain.\n");
    }
#ifdef _AIX
    /* 
     * This is a temporary KLUDGE to get around the problem where some people
     * might start zwgc in their ~/.startup.X and it hangs on the RISC/6000.
     * Apparently, the call to tgetent() with the Athena console window causes
     * the process to get stopped on tty access.  Since the terminal type is
     * "dumb" (set by tcsh), we can pretty much assume there isn't anything
     * to setup from the termcap information.
     */
    else if (!strcmp(term, "dumb")) { }
#endif
    else {
	tgetent(tc_buf, term);
    
	/* Step 1: get all of {rv,bold,u,bell,blink} that are available. */

	/* We cheat here, and ignore the padding (if any) specified for
	   the mode-change strings (it's a real pain to do "right") */

	tmp = tgetstr("pc", &p);
	PC = (tmp) ? *tmp : 0;
	tmp = tgetstr("md", &p);
	if (tmp) {	/* bold ? */
	    EXPAND("B.bold");
	    tmp = tgetstr("me",&p);
	    EXPAND("E.bold");
	}
	tmp = tgetstr("mr", &p);
	if (tmp) {	/* reverse video? */
	    EXPAND("B.rw");
	    tmp = tgetstr("me", &p);
	    EXPAND("E.rw");
	}
	tmp = tgetstr("bl", &p);
	if (tmp) {	/* Bell ? */
	    EXPAND("B.bell");
	    TD_SET("E.bell", NULL);
	}
	tmp = tgetstr("mb", &p);
	if (tmp) {	/* Blink ? */
	    EXPAND("B.blink");
	    tmp = tgetstr("me", &p);
	    EXPAND("E.blink");
	}
	tmp = tgetstr("us", &p);
	if (tmp) {	/* Underline ? */
	    EXPAND("B.u");
	    tmp = tgetstr("ue", &p);
	    EXPAND("E.u");
	}
	tmp = tgetstr("so", &p);
	if (tmp) {	/* Standout ? */
	    EXPAND("B.so");
	    tmp = tgetstr("se", &p);
	    EXPAND("E.so");
	}
    }    
    /* Step 2: alias others to the nearest substitute */
    
    /* Bold = so, else rv, else ul */
    if (NULL == string_dictionary_Lookup(termcap_dict, "B.bold")) {
	if((b = string_dictionary_Lookup(termcap_dict, "B.so"))) {
	    TD_SET("B.bold", b->value);
	    TD_SET("E.bold",
		   string_dictionary_Lookup(termcap_dict, "E.so")->value);
	} else if ((b = string_dictionary_Lookup(termcap_dict, "B.rv"))) {
	    TD_SET("B.bold", b->value);
	    TD_SET("E.bold",
		   string_dictionary_Lookup(termcap_dict, "E.rv")->value);
	} else if ((b = string_dictionary_Lookup(termcap_dict,"B.u"))) {
	    TD_SET("B.bold", b->value);
	    TD_SET("E.bold",
		   string_dictionary_Lookup(termcap_dict, "E.u")->value);
	}
    }
    
    /* Bell = ^G */
    if (NULL == string_dictionary_Lookup(termcap_dict, "B.bell")) {
	TD_SET("B.bell", "\007");
	TD_SET("E.bell", NULL);
    }
    
    /* Underline -> nothing */
    /* Blink -> nothing */
    
    return(0);
}

/***************************************************************************/




static int
fixed_string_eq(string pattern,
		char *text,
		int text_length)
{
    while (*pattern && text_length>0 && *pattern == *text) {
	pattern++;
	text++;
	text_length--;
    }

    return(!*pattern && !text_length);
}

typedef struct _tty_str_info {
    struct _tty_str_info *next;

    char *str;
    int len;

    char alignment; /* 'l', 'c', 'r', or ' ' to indicate newline */
    unsigned int bold_p : 1;
    unsigned int italic_p : 1;
    unsigned int bell_p : 1;
    unsigned int ignore: 1;
} tty_str_info;

const char *info_default_string = "";

static void
free_info(tty_str_info *info)
{
    tty_str_info *next_info;

    while (info) {
	next_info = info->next;
	if (info->str != info_default_string)
	    free(info->str);
	free(info);
	info = next_info;
    }
}

static int
do_mode_change(tty_str_info *current_mode_p,
	       char *text,
	       int text_length)
{
    /* alignment commands: */
    if (fixed_string_eq("left", text, text_length) ||
	fixed_string_eq("l", text, text_length))
      current_mode_p->alignment = 'l';
    else if (fixed_string_eq("center", text, text_length) ||
	fixed_string_eq("c", text, text_length))
      current_mode_p->alignment = 'c';
    else if (fixed_string_eq("right", text, text_length) ||
	fixed_string_eq("r", text, text_length))
      current_mode_p->alignment = 'r';

    /* font commands: */
    else if (fixed_string_eq("bold", text, text_length) ||
	     fixed_string_eq("b", text, text_length))
      current_mode_p->bold_p = 1;
    else if (fixed_string_eq("italic", text, text_length) ||
	     fixed_string_eq("i", text, text_length))
      current_mode_p->italic_p = 1;
    else if (fixed_string_eq("roman", text, text_length)) {
	current_mode_p->bold_p = 0;
	current_mode_p->italic_p = 0;
    } else if (fixed_string_eq("beep", text, text_length)) {
	current_mode_p->bell_p = 1;
	return 1;
    }

    /* commands ignored in tty mode: */
    else if (fixed_string_eq("color", text, text_length) ||
	     fixed_string_eq("font", text, text_length)) {
	current_mode_p->ignore = 1;
    } 
    return 0;
}

static void
zwgc_transliterate(char *in, int inlen, char **out, int *outlen){
    int retval = 0;
    string notice_charset = var_get_variable("notice_charset");
    string tty_charset = var_get_variable("tty_charset");

    if (string_Eq(notice_charset, "UNKNOWN") ||
	string_Eq(notice_charset, tty_charset) ||
	(retval = ZTransliterate(in, inlen,
				 notice_charset, tty_charset,
				 out, outlen)) != 0) {
	*out = string_CreateFromData(in, inlen);
	*outlen = inlen;
    }
    if (retval != 0)
	var_set_variable("error", strerror(retval));
}

static tty_str_info *
convert_desc_to_tty_str_info(desctype *desc)
{
    tty_str_info *temp;
    tty_str_info *result = NULL;
    tty_str_info *last_result_block = NULL;
    int isbeep, did_beep = 0;

#if !defined(SABER) && defined(__STDC__)
    tty_str_info current_mode = { NULL, "", 0, 'l', 0 , 0, 0, 0};
#else
    /* This is needed due to a bug in saber, and lack of pre-ANSI support. */
    tty_str_info current_mode;

    current_mode.next = NULL;
    current_mode.str = info_default_string; /* "" */
    current_mode.len = 0;
    current_mode.alignment = 'l';
    current_mode.bold_p = 0;
    current_mode.italic_p = 0;
    current_mode.bell_p = 0;
    current_mode.ignore = 0;
#endif

    for (; desc->code!=DT_EOF; desc=desc->next) {
	isbeep = 0;
	/* Handle environments: */
	if (desc->code == DT_ENV) {
	    /* PUSH! */
	    temp = (tty_str_info *)malloc(sizeof(struct _tty_str_info));
	    *temp = current_mode;
	    current_mode.next = temp;

	    isbeep = do_mode_change(&current_mode, desc->str, desc->len);
	    if (!isbeep || did_beep)
		continue;	/* process one beep, ignore other envs */
	} else if (desc->code == DT_END) {
	    /* POP! */
	    temp = current_mode.next;
	    current_mode = *temp;
	    free(temp);
	    continue;
	}

	/* Add new block (call it temp) to result: */
	temp = (tty_str_info *)malloc(sizeof(struct _tty_str_info));
	*temp = current_mode;
	if (last_result_block) {
	    last_result_block->next = temp;
	    last_result_block = temp;
	} else {
	    result = temp;
	    last_result_block = temp;
	}

	if (isbeep) {
	    /* special processing: need to insert a bell */
	    string_dictionary_binding *b;
	    b = string_dictionary_Lookup(termcap_dict,"B.bell");
	    if (b) {
		temp->str = strdup(b->value);
		temp->len = string_Length(temp->str);
	    } else
		/* shouldn't get here! */
		abort();
	    did_beep++;
	    continue;
	}
	if (desc->code == DT_STR) {
	    /* just combine string info with current mode: */
	    zwgc_transliterate(desc->str, desc->len, &temp->str, &temp->len);
	} else if (desc->code == DT_NL) {
	    /* make the new block a ' ' alignment block with an empty string */
	    temp->alignment = ' ';
	    temp->len = 0;
	    temp->ignore = 0;
	}
    }

    if (last_result_block)
      last_result_block->next = NULL;

    return(result);
}

#define  max(a,b)                ((a)>(b)?(a):(b))

static int
line_width(int left_width,
	   int center_width,
	   int right_width)
{
    if (center_width>0) {
	if (left_width==0 && right_width==0)
	  return(center_width);
	return(center_width+2+max(left_width,right_width)*2);
    } else {
	if (left_width && right_width)
	  return(1+left_width+right_width);
	else
	  return(left_width+right_width);
    }
}

static int
calc_max_line_width(tty_str_info *info)
{
    int max_line_width = 0;
    int left = 0;
    int center = 0;
    int right = 0;

    for (; info; info=info->next) {
	if (info->ignore)
	    continue;
	switch (info->alignment) {
	  case 'l':
	    left += info->len;
	    break;

	  case 'c':
	    center += info->len;
	    break;

	  case 'r':
	    right += info->len;
	    break;

	  case ' ':
#ifdef DEBUG
	    if (zwgc_debug)
	      printf("width: %d %d %d = %d\n", left, center, right,
		     line_width(left, center, right));
#endif
	    max_line_width = max(max_line_width,
				 line_width(left, center, right));
	    left = center = right = 0;
	    break;
	}
    }

#ifdef DEBUG
    if (zwgc_debug)
      printf("width: %d %d %d = %d\n", left, center, right,
	     line_width(left, center, right));
#endif
    max_line_width = max(max_line_width,
			 line_width(left, center, right));

    return(max_line_width);
}

string
tty_filter(string text,
	   int use_fonts)
{
    string text_copy = string_Copy(text);
    string result_so_far = string_Copy("");
    desctype *desc;
    int number_of_strs;
    int number_of_lines;
    tty_str_info *info, *info_head;
    int max_line_width;

    desc = disp_get_cmds(text_copy, &number_of_strs, &number_of_lines);
    info_head = info = convert_desc_to_tty_str_info(desc);
    free_desc(desc);

#ifdef DEBUG
    if (zwgc_debug)
      { tty_str_info *ptr;
	for (ptr=info; ptr; ptr=ptr->next) {
	    printf("%c: %s %s %s <%s>\n", ptr->alignment,
		   ptr->bold_p ? "(bold)" : "",
		   ptr->italic_p ? "(italic)" : "",
		   ptr->bell_p ? "(bell)" : "",
		   string_CreateFromData(ptr->str, ptr->len));
	}
    }
#endif

    max_line_width = calc_max_line_width(info);
    dprintf1("max width = %d\n", max_line_width);

    while (info) {
	string left, center, right;
	int left_width, center_width, right_width;
	char *temp;

	left_width = center_width = right_width = 0;
	left = string_Copy("");
	center = string_Copy("");
	right = string_Copy("");

	for (; info && info->alignment!=' '; info=info->next) {
	    string item;

	    if (info->ignore)
		continue;

	    item = string_Copy("");
	    
	    if (info->bold_p && use_fonts) {
		temp = string_dictionary_Fetch(termcap_dict, "B.bold");
		if (temp)
		  item = string_Concat2(item, temp);
	    } else if (info->italic_p && use_fonts) {
		temp = string_dictionary_Fetch(termcap_dict, "B.u");
		if (temp)
		  item = string_Concat2(item, temp);
	    }
	    temp = string_CreateFromData(info->str, info->len);
	    item = string_Concat2(item, temp);
	    free(temp);

	    if (info->bold_p && use_fonts) {
		temp = string_dictionary_Fetch(termcap_dict, "E.bold");
		if (temp)
		  item = string_Concat2(item, temp);
	    } else if (info->italic_p && use_fonts) {
		temp = string_dictionary_Fetch(termcap_dict, "E.u");
		if (temp)
		  item = string_Concat2(item, temp);
	    }

	    switch (info->alignment) {
	      default:
	      case 'l':
		left = string_Concat2(left, item);
		left_width += info->len;
		break;

	      case 'c':
		center = string_Concat2(center, item);
		center_width += info->len;
		break;

	      case 'r':
		right = string_Concat2(right, item);
		right_width += info->len;
		break;
	    }
	    free(item);
	}

	result_so_far = string_Concat2(result_so_far, left);
	if (center_width)
	  while (left_width < (max_line_width-center_width)/2 ) {
	      result_so_far = string_Concat2(result_so_far, " ");
	      left_width++;
	  }
	result_so_far = string_Concat2(result_so_far, center);
	left_width += center_width;

	if (right_width)
	  while (left_width<max_line_width-right_width) {
	      result_so_far = string_Concat2(result_so_far, " ");
	      left_width++;
	  }
	result_so_far = string_Concat2(result_so_far, right);
	free(left);  free(center);  free(right);

	if (info && info->alignment == ' ') {
	    info = info->next;
	    result_so_far = string_Concat2(result_so_far, "\r\n");
	}
    }

    free_info(info_head);
    free(text_copy);
    if (number_of_lines &&
	(result_so_far[string_Length(result_so_far)-1] != '\n'))
	/* CRLF-terminate all results */
	result_so_far = string_Concat2(result_so_far, "\r\n");
    return(result_so_far);
}
