/* character level styles for Lynx
 * (c) 1996 Rob Partington -- donated to the Lyncei (if they want it :-)
 * $Id: LYStyle.c,v 1.2 2002/12/23 20:04:57 corinna Exp $
 */
#include <HTUtils.h>
#include <HTML.h>
#include <LYGlobalDefs.h>

#include <LYStructs.h>
#include <LYReadCFG.h>
#include <LYCurses.h>
#include <LYCharUtils.h>
#include <LYUtils.h>		/* defines TABLESIZE */
#include <AttrList.h>
#include <SGML.h>
#include <HTMLDTD.h>

/* Hash table definitions */
#include <LYHash.h>
#include <LYStyle.h>

#include <LYexit.h>
#include <LYLeaks.h>
#include <LYStrings.h>

#ifdef USE_COLOR_STYLE

PRIVATE void style_initialiseHashTable NOPARAMS;

/* stack of attributes during page rendering */
PUBLIC int last_styles[128];
PUBLIC int last_colorattr_ptr = 0;

PUBLIC bucket hashStyles[CSHASHSIZE];
PUBLIC bucket special_bucket =
{
    "<special>", /* in order something to be in trace. */
    0, 0, 0, 0, NULL
};
PUBLIC bucket nostyle_bucket =
{
    "<NOSTYLE>", /* in order something to be in trace. */
    0, 0, 0, 0, NULL
};

PUBLIC int cached_tag_styles[HTML_ELEMENTS];
PUBLIC int current_tag_style;
PUBLIC BOOL force_current_tag_style = FALSE;
PUBLIC char* forced_classname;
PUBLIC BOOL force_classname;

/* Remember the hash codes for common elements */
PUBLIC int s_a			= NOSTYLE;
PUBLIC int s_aedit		= NOSTYLE;
PUBLIC int s_aedit_arr		= NOSTYLE;
PUBLIC int s_aedit_pad		= NOSTYLE;
PUBLIC int s_aedit_sel		= NOSTYLE;
PUBLIC int s_alert		= NOSTYLE;
PUBLIC int s_alink		= NOSTYLE;
PUBLIC int s_curedit		= NOSTYLE;
PUBLIC int s_forw_backw		= NOSTYLE;
PUBLIC int s_normal		= NOSTYLE;
PUBLIC int s_prompt_edit	= NOSTYLE;
PUBLIC int s_prompt_edit_arr	= NOSTYLE;
PUBLIC int s_prompt_edit_pad	= NOSTYLE;
PUBLIC int s_prompt_sel		= NOSTYLE;
PUBLIC int s_status		= NOSTYLE;
PUBLIC int s_title		= NOSTYLE;
PUBLIC int s_whereis		= NOSTYLE;
PUBLIC int s_menu_frame		= NOSTYLE;
PUBLIC int s_menu_bg		= NOSTYLE;
PUBLIC int s_menu_number	= NOSTYLE;
PUBLIC int s_menu_entry		= NOSTYLE;
PUBLIC int s_menu_active	= NOSTYLE;
PUBLIC int s_menu_sb		= NOSTYLE;

#ifdef USE_SCROLLBAR
PUBLIC int s_sb_aa		= NOSTYLE;
PUBLIC int s_sb_bar		= NOSTYLE;
PUBLIC int s_sb_bg		= NOSTYLE;
PUBLIC int s_sb_naa		= NOSTYLE;
#endif

/* start somewhere safe */
#define MAX_COLOR 16
PRIVATE int colorPairs = 0;

#ifdef USE_BLINK
#  define MAX_BLINK	2
#  define M_BLINK	A_BLINK
#else
#  define MAX_BLINK	1
#  define M_BLINK	0
#endif

PRIVATE unsigned char our_pairs[2][MAX_BLINK][MAX_COLOR][MAX_COLOR];

/*
 * Parse a string containing a combination of video attributes and color.
 */
PRIVATE void parse_either ARGS4(
    char *,	attrs,
    int,	dft_color,
    int *,	monop,
    int *,	colorp)
{
    int value;

    while (*attrs != '\0') {
	char *next = strchr(attrs, '+');
	char save = (next != NULL) ? *next : '\0';
	if (next == NULL)
	    next = attrs + strlen(attrs);

	if (save != 0)	/* attrs might be a constant string */
	    *next = '\0';
	if ((value = string_to_attr(attrs)) != 0)
	    *monop |= value;
	else if (colorp != 0
	 && (value = check_color(attrs, dft_color)) != ERR_COLOR)
	    *colorp = value;

	attrs = next;
	if (save != '\0')
	    *attrs++ = save;
    }
}

/* icky parsing of the style options */
PRIVATE void parse_attributes ARGS5(
    char *,	mono,
    char *,	fg,
    char *,	bg,
    int,	style,
    char *,	element)
{
    int mA = A_NORMAL;
    int fA = default_fg;
    int bA = default_bg;
    int cA = A_NORMAL;
    int newstyle = hash_code(element);

    CTRACE2(TRACE_STYLE, (tfp, "CSS(PA):style d=%d / h=%d, e=%s\n", style, newstyle, element));

    parse_either(mono, ERR_COLOR, &mA, (int *)0);
    parse_either(bg, default_bg, &cA, &bA);
    parse_either(fg, default_fg, &cA, &fA);

    if (style == -1) {			/* default */
	CTRACE2(TRACE_STYLE, (tfp, "CSS(DEF):default_fg=%d, default_bg=%d\n", fA, bA));
	default_fg = fA;
	default_bg = bA;
	default_color_reset = TRUE;
	return;
    }
    if (fA == NO_COLOR) {
	bA = NO_COLOR;
    } else if (COLORS) {
#ifdef USE_BLINK
	if (term_blink_is_boldbg) {
	    if (fA >= COLORS)
		cA = A_BOLD;
	    if (bA >= COLORS)
		cA |= M_BLINK;
	} else
#endif
	if (fA >= COLORS || bA >= COLORS)
	    cA = A_BOLD;
	if (fA >= COLORS)
	    fA %= COLORS;
	if (bA >= COLORS)
	    bA %= COLORS;
    } else {
	cA = A_BOLD;
	fA = NO_COLOR;
	bA = NO_COLOR;
    }

    /*
     * If we have colour, and space to create a new colour attribute,
     * and we have a valid colour description, then add this style
     */
    if (lynx_has_color && colorPairs < COLOR_PAIRS-1 && fA != NO_COLOR)
    {
	int curPair;

	if (fA < MAX_COLOR
	 && bA < MAX_COLOR
	 && our_pairs[!!(cA & A_BOLD)][!!(cA & A_BLINK)][fA][bA])
	    curPair = our_pairs[!!(cA & A_BOLD)][!!(cA & M_BLINK)][fA][bA] - 1;
	else {
	    curPair = ++colorPairs;
	    init_pair((short)curPair, (short)fA, (short)bA);
	    if (fA < MAX_COLOR
	     && bA < MAX_COLOR
	     && curPair < 255)
		our_pairs[!!(cA & A_BOLD)][!!(cA & M_BLINK)][fA][bA] = curPair + 1;
	}
	CTRACE2(TRACE_STYLE, (tfp, "CSS(CURPAIR):%d\n", curPair));
	if (style < DSTYLE_ELEMENTS)
	    setStyle(style, COLOR_PAIR(curPair)|cA, cA, mA);
	setHashStyle(newstyle, COLOR_PAIR(curPair)|cA, cA, mA, element);
    }
    else
    {
	if (lynx_has_color && fA != NO_COLOR) {
	    CTRACE2(TRACE_STYLE, (tfp, "CSS(NC): maximum of %d colorpairs exhausted\n", COLOR_PAIRS - 1));
	}
	/* only mono is set */
	if (style < DSTYLE_ELEMENTS)
	    setStyle(style, -1, -1, mA);
	setHashStyle(newstyle, -1, -1, mA, element);
    }
}

/* parse a style option of the format
 * STYLE:<OBJECT>:FG:BG
 */
PRIVATE void parse_style ARGS1(char*, param)
{
    static struct {
	char *name;
	int style;
	int *set_hash;
    } table[] = {
	{ "default",		-1,			0 }, /* default fg/bg */
	{ "alink",		DSTYLE_ALINK,		0 }, /* active link */
	{ "a",			DSTYLE_LINK,		0 }, /* normal link */
	{ "a",			HTML_A,			0 }, /* normal link */
	{ "status",		DSTYLE_STATUS,		0 }, /* status bar */
	{ "label",		DSTYLE_OPTION,		0 }, /* [INLINE]'s */
	{ "value",		DSTYLE_VALUE,		0 }, /* [INLINE]'s */
	{ "high",		DSTYLE_HIGH,		0 }, /* [INLINE]'s */
	{ "normal",		DSTYLE_NORMAL,		0 },
	{ "candy",		DSTYLE_CANDY,		0 }, /* [INLINE]'s */
	{ "whereis",		DSTYLE_WHEREIS,		&s_whereis },
	{ "edit.active.pad",	DSTYLE_ELEMENTS,	&s_aedit_pad },
	{ "edit.active.arrow",	DSTYLE_ELEMENTS,	&s_aedit_arr },
	{ "edit.active.marked",	DSTYLE_ELEMENTS,	&s_aedit_sel },
	{ "edit.active",	DSTYLE_ELEMENTS,	&s_aedit },
	{ "edit.current",	DSTYLE_ELEMENTS,	&s_curedit },
	{ "edit.prompt.pad",	DSTYLE_ELEMENTS,	&s_prompt_edit_pad },
	{ "edit.prompt.arrow",	DSTYLE_ELEMENTS,	&s_prompt_edit_arr },
	{ "edit.prompt.marked",	DSTYLE_ELEMENTS,	&s_prompt_sel },
	{ "edit.prompt",	DSTYLE_ELEMENTS,	&s_prompt_edit },
	{ "forwbackw.arrow",	DSTYLE_ELEMENTS,	&s_forw_backw },
	{ "menu.frame",		DSTYLE_ELEMENTS,	&s_menu_frame },
	{ "menu.bg",		DSTYLE_ELEMENTS,	&s_menu_bg },
	{ "menu.n",		DSTYLE_ELEMENTS,	&s_menu_number },
	{ "menu.entry",		DSTYLE_ELEMENTS,	&s_menu_entry },
	{ "menu.active",	DSTYLE_ELEMENTS,	&s_menu_active },
	{ "menu.sb",		DSTYLE_ELEMENTS,	&s_menu_sb },
    };
    unsigned n;
    BOOL found = FALSE;

    char *buffer = strdup(param);
    char *tmp = strchr(buffer, ':');
    char *element, *mono, *fg, *bg;

    if(!tmp)
    {
	fprintf (stderr, gettext("\
Syntax Error parsing style in lss file:\n\
[%s]\n\
The line must be of the form:\n\
OBJECT:MONO:COLOR (ie em:bold:brightblue:white)\n\
where OBJECT is one of EM,STRONG,B,I,U,BLINK etc.\n\n"), buffer);
	if (!dump_output_immediately) {
	    exit_immediately(EXIT_FAILURE);
	}
	exit(1);
    }
    strtolower(buffer);
    *tmp = '\0';
    element = buffer;

    mono = tmp + 1;
    tmp = strchr(mono, ':');

    if (!tmp)
    {
	fg = "nocolor";
	bg = "nocolor";
    }
    else
    {
	*tmp = '\0';
	fg = tmp+1;
	tmp = strchr(fg, ':');
	if (!tmp)
	    bg = "default";
	else
	{
	    *tmp = '\0';
	    bg = tmp + 1;
	}
    }

    CTRACE2(TRACE_STYLE, (tfp, "CSSPARSE:%s => %d %s\n",
		element, hash_code(element),
		(hashStyles[hash_code(element)].name ? "used" : "")));

    strtolower(element);

    /*
    * We use some pseudo-elements, so catch these first
    */
    for (n = 0; n < TABLESIZE(table); n++) {
	if (!strcasecomp(element, table[n].name)) {
	    parse_attributes(mono, fg, bg, table[n].style, table[n].name);
	    if (table[n].set_hash != 0)
		*(table[n].set_hash) = hash_code(table[n].name);
	    found = TRUE;
	    break;
	}
    }

    if (found) {
	;
    }
    else if (!strcasecomp(element, "normal")) /* added - kw */
    {
	parse_attributes(mono,fg,bg,DSTYLE_NORMAL,"html");
	s_normal  = hash_code("html"); /* rather bizarre... - kw */
    }
    /* Ok, it must be a HTML element, so look through the list until we
    * find it
    */
    else
    {
	int element_number = -1;
	HTTag * t = SGMLFindTag(&HTML_dtd, element);
	if (t && t->name) {
	    element_number = t - HTML_dtd.tags;
	}
	if (element_number >= HTML_A &&
	    element_number < HTML_ELEMENTS)
	    parse_attributes(mono,fg,bg, element_number+STARTAT,element);
	else
	    parse_attributes(mono,fg,bg, DSTYLE_ELEMENTS,element);
    }
    FREE(buffer);
}

#ifdef LY_FIND_LEAKS
PRIVATE void free_colorstylestuff NOARGS
{
    style_initialiseHashTable();
    style_deleteStyleList();
}
#endif

/*
 * initialise the default style sheet
 * This should be able to be read from a file in CSS format :-)
 */
PRIVATE void initialise_default_stylesheet NOARGS
{
    static CONST char *table[] = {
	"a:bold:green",
	"alert:bold:yellow:red",
	"alink:reverse:yellow:black",
	"label:normal:magenta",
	"status:reverse:yellow:blue",
	"title:normal:magenta",
	"whereis:reverse+underline:magenta:cyan"
    };
    unsigned n;
    char temp[80];
    CTRACE((tfp, "initialize_default_stylesheet\n"));
    for (n = 0; n < TABLESIZE(table); n++) {
	parse_style(strcpy(temp, table[n]));
    }
}

/* Set all the buckets in the hash table to be empty */
PRIVATE void style_initialiseHashTable NOARGS
{
    int i;
    static int firsttime = 1;

    for (i = 0; i <CSHASHSIZE; i++)
    {
	if (firsttime)
	    hashStyles[i].name = NULL;
	else
	    FREE(hashStyles[i].name);
	hashStyles[i].color = 0;
	hashStyles[i].cattr = 0;
	hashStyles[i].mono  = 0;
    }
    if (firsttime) {
	firsttime = 0;
#ifdef LY_FIND_LEAKS
	atexit(free_colorstylestuff);
#endif
    }
    s_alink  = hash_code("alink");
    s_a      = hash_code("a");
    s_status = hash_code("status");
    s_alert  = hash_code("alert");
    s_title  = hash_code("title");
#ifdef USE_SCROLLBAR
    s_sb_bar = hash_code("scroll.bar");
    s_sb_bg  = hash_code("scroll.back");
    s_sb_aa  = hash_code("scroll.arrow");
    s_sb_naa = hash_code("scroll.noarrow");
#endif
}

/* because curses isn't started when we parse the config file, we
 * need to remember the STYLE: lines we encounter and parse them
 * after curses has started
 */
PRIVATE HTList *lss_styles = NULL;

PUBLIC void parse_userstyles NOARGS
{
    static BOOL first = TRUE;
    char *name;
    HTList *cur = lss_styles;

    colorPairs = 0;
    style_initialiseHashTable();

    /* set our styles to be the same as vanilla-curses-lynx */
    if (HTList_isEmpty(cur)) {
	initialise_default_stylesheet();
    } else {
	while ((name = HTList_nextObject(cur)) != NULL) {
	    CTRACE2(TRACE_STYLE, (tfp, "LSS:%s\n", name ? name : "!?! empty !?!"));
	    if (name != NULL)
		parse_style(name);
	}
    }

#define dft_style(a,b) if (a == NOSTYLE) a = b

    dft_style(s_prompt_edit,		s_normal);
    dft_style(s_prompt_edit_arr,	s_prompt_edit);
    dft_style(s_prompt_edit_pad,	s_prompt_edit);
    dft_style(s_prompt_sel,		s_prompt_edit);
    dft_style(s_aedit,			s_alink);
    dft_style(s_aedit_arr,		s_aedit);
    dft_style(s_aedit_pad,		s_aedit);
    dft_style(s_curedit,		s_aedit);
    dft_style(s_aedit_sel,		s_aedit);
    dft_style(s_menu_bg,		s_normal);
    dft_style(s_menu_entry,		s_menu_bg);
    dft_style(s_menu_frame,		s_menu_bg);
    dft_style(s_menu_number,		s_menu_bg);
    dft_style(s_menu_active,		s_alink);
}


/* Add a STYLE: option line to our list.  Process "default:" early
   for it to have the same semantic as other lines: works at any place
   of the style file, the first line overrides the later ones. */
PRIVATE void HStyle_addStyle ARGS1(char*, buffer)
{
    char *name = NULL;

    CTRACE((tfp, "HStyle_addStyle(%s)\n", buffer));
    StrAllocCopy(name, buffer);
    if (lss_styles == NULL)
	lss_styles = HTList_new();
    strtolower(name);
    if (!strncasecomp(name, "default:", 8)) /* default fg/bg */
    {
	CTRACE2(TRACE_STYLE, (tfp, "READCSS.default%s:%s\n",
		 (default_color_reset ? ".ignore" : ""),
		 name ? name : "!?! empty !?!"));
	if (!default_color_reset)
	    parse_style(name);
	return;				/* do not need to process it again */
    }
    CTRACE2(TRACE_STYLE, (tfp, "READCSS:%s\n", name ? name : "!?! empty !?!"));
    HTList_addObject (lss_styles, name);
}

PUBLIC void style_deleteStyleList NOARGS
{
    char *name;
    while ((name = HTList_removeLastObject(lss_styles)) != NULL)
	FREE(name);
    HTList_delete (lss_styles);
    lss_styles = NULL;
}

PRIVATE int style_readFromFileREC ARGS2(
    char *,	lss_filename,
    char *,	parent_filename)
{
    FILE *fh;
    char *buffer = NULL;
    int len;

    CTRACE2(TRACE_STYLE, (tfp, "CSS:Reading styles from file: %s\n", lss_filename ? lss_filename : "?!? empty ?!?"));
    if (lss_filename == NULL || *lss_filename == '\0')
	return -1;
    if ((fh = LYOpenCFG(lss_filename, parent_filename, LYNX_LSS_FILE)) == 0) {
	/* this should probably be an alert or something */
	CTRACE2(TRACE_STYLE, (tfp, "CSS:Can't open style file '%s', using defaults\n", lss_filename));
	return -1;
    }

    if (parent_filename == 0) {
	style_initialiseHashTable();
	style_deleteStyleList();
    }

    while (LYSafeGets(&buffer, fh) != NULL) {
	LYTrimTrailing(buffer);
	LYTrimTail(buffer);
	LYTrimHead(buffer);
	if (!strncasecomp(buffer,"include:",8))
	    style_readFromFileREC(buffer+8, lss_filename);
	else if (buffer[0] != '#' && (len = strlen(buffer)) > 0)
	    HStyle_addStyle(buffer);
    }

    LYCloseInput (fh);
    if ((parent_filename == 0) && LYCursesON)
	parse_userstyles();
    return 0;
}

PUBLIC int style_readFromFile ARGS1(char*, filename)
{
    return style_readFromFileREC(filename, (char *)0);
}

/* Used in HTStructured methods: - kw */

PUBLIC void TrimColorClass ARGS3(
    CONST char *,	tagname,
    char *,		styleclassname,
    int *,		phcode)
{
    char *end, *start=NULL, *lookfrom;
    char tmp[64];

    sprintf(tmp, ";%.*s", (int) sizeof(tmp) - 3, tagname);
    strtolower(tmp);

    if ((lookfrom = styleclassname) != 0) {
	do {
	    end = start;
	    start = strstr(lookfrom, tmp);
	    if (start)
		lookfrom = start + 1;
	}
	while (start);
	/* trim the last matching element off the end
	** - should match classes here as well (rp)
	*/
	if (end)
	    *end='\0';
    }
    *phcode = hash_code(lookfrom && *lookfrom ? lookfrom : &tmp[1]);
}

/* This function is designed as faster analog to TrimColorClass.
   It assumes that tag_name is present in stylename! -HV
*/
PUBLIC void FastTrimColorClass ARGS5 (
	    CONST char*,	 tag_name,
	    int,		 name_len,
	    char*,		 stylename,
	    char**,		 pstylename_end,/*will be modified*/
	    int*,		 phcode)	/*will be modified*/
{
    char* tag_start = *pstylename_end;
    BOOLEAN found = FALSE;

    CTRACE2(TRACE_STYLE,
	    (tfp, "STYLE.fast-trim: [%s] from [%s]: ",
		  tag_name, stylename));
    while (tag_start >= stylename)
    {
	for (; (tag_start >= stylename) && (*tag_start != ';') ; --tag_start)
	    ;
	if ( !strncasecomp(tag_start+1, tag_name, name_len) ) {
	    found = TRUE;
	    break;
	}
	--tag_start;
    }
    if (found) {
	*tag_start = '\0';
	*pstylename_end = tag_start;
    }
    CTRACE2(TRACE_STYLE, (tfp, found ? "success.\n" : "failed.\n"));
    *phcode = hash_code(tag_start+1);
}

 /* This is called each time lss styles are read. It will fill
    each elt of 'cached_tag_styles' -HV
 */
PUBLIC void cache_tag_styles NOARGS
{
    char buf[200];
    int i;

    for (i = 0; i < HTML_ELEMENTS; ++i)
    {
	strcpy(buf, HTML_dtd.tags[i].name);
	LYLowerCase(buf);
	cached_tag_styles[i] = hash_code(buf);
    }
}

#endif /* USE_COLOR_STYLE */
