// Filename:   color.C
// Purpose:    the methods for the colorizing object
// Author: Greg Shaw
// Created:    1/29/95

#ifndef _COLOR_C_
#define _COLOR_C_

#include "bbshdr.h"

static int inited;
static int     fore_c;          // foreground color
static int     back_c;          // background color
// terminal attributes
static Attributes      attributes;
static int     location[2];     // cursor location
static int     s_location[2];   // 'saved' location
// default attribute color
static int     default_attribute;
// default foreground color
static int     default_foreground;
// default background color
static int     default_background;

/*
This file 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, or (at your option) any
later version.

In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file with other programs, and to distribute
those programs without any restriction coming from the use of this
file.  (The General Public License restrictions do apply in other
respects; for example, they cover modification of the file, and
distribution when not linked into another program.)

This file 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; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

// top level 'User' of the BBS
extern User user;
// monitor connection object
extern moncon mon_obj;


// Method:     ansi_color_string
// Purpose:    return a string which sets the desired color information
// Input:      fore - foreground color
//     back - background color
//     related attributes
// Output:     an ANSI string
// Author:     Greg Shaw
// Created:     1/29/95

char *color::ansi_color_string(int fore, int back, Attributes attr)
{

	int col_fore = 0;           // ansi foreground
	int col_back = 0;           // ansi foreground
	int attribute = 0;          // sum attributes


	// setup attributes
	if (attr.normal)            // normal overrides all attributes
	{
		attribute = 0;
	}
	else
	{
		if (attr.dim)           // dim and bold are mutually exclusive
			attr.bold = 0;
		if (attr.bold)
			attribute += ANSI_BOLD;
		if (attr.blink)
			attribute += ANSI_BLINK;
		if (attr.inverse)
			attribute += ANSI_INVERSE;
		if (attr.underline)
			if (!user.has_color())
				attribute += ANSI_UNDERLINE_MONO;
		else                    // promote underline in color to bold
			attribute += ANSI_BOLD;

	}
	col_fore = fore_c + 29;     // 29 difference between us and ANSI
	col_back = back_c + 39;     // 39 difference between us and ANSI
	if (attribute != 0)
		sprintf(color_string,"%c[%d;%d;%dm",ESC,attribute,col_fore,col_back);
	else                        // color only
		sprintf(color_string,"%c[0;%d;%dm",ESC,col_fore,col_back);
	return(color_string);
};


// Method:     background
// Purpose:    attempt to set the background color
// Input:      val - the color to set the background to:
//     1 - black
//     2 - red
//     3 - green
//     4 - yellow
//     5 - blue
//     6 - magenta
//         7 - cyan
//     8 - white
// Output:     if the terminal type is ansi, it sets the background color
//     to the appropriate color
// Author:     Greg Shaw
// Notes:  At this point, only those colors supported by ANSI are used.
//     (if true color is needed in the future, this function will
//     change radically)
// Created:    1/29/95

char *color::background(int val)
{
	char tmpstr[255];

	if (has_ansi())
	{                           // support only ansi at this point
		if (val < BLACK || val > WHITE)
		{
			sprintf(tmpstr,"color: invalid background color type (%d) %c found",val,val);
			ap_log(tmpstr);
			color_string[0] = 0;
			return(color_string);
		}
		back_c = val;
		// send the string to set color attributes
		return(ansi_color_string(fore_c,back_c,attributes));
	}
	color_string[0] = 0;
	return(color_string);
};


// Method:     blink
// Purpose:    (re) set blinking
// Input:      none
// Output:     the blinking code is sent.
// Author:     Greg Shaw
// Created:     1/29/95

char *color::blink(void)
{
	attributes.blink ^= 1;      // reset mode
	check_normal();             // reset 'normal' bit as necessary
	return(ansi_color_string(fore_c,back_c,attributes));
};


// Method:     bold
// Purpose:    (re) set bold
// Input:      none
// Output:     the bold code is sent.
// Author:     Greg Shaw
// Created:     1/29/95

char *color::bold(void)
{
	attributes.bold ^= 1;       // reset mode
	check_normal();             // reset 'normal' bit as necessary
	return(ansi_color_string(fore_c,back_c,attributes));
};


// Method:     check_normal
// Purpose:    set the normal bit after an attribute change as necessary
// Input:      none
// Output:     normal is set if nothing else is set
// Author:     Greg Shaw
// Created:     1/29/95

void color::check_normal(void)
{
	if ((attributes.blink || attributes.bold || attributes.inverse ||
		attributes.underline || attributes.dim))
	attributes.normal = 0;      // off if any other set
	else
		attributes.normal = 1;  // on if everything else clear
}


// Method:     cmove
// Purpose:    generate an ansi movement command
// Input:      x,y - location to move to
// Output:     an absolute ansi movement command is generated
// Author:     Greg Shaw
// Created:     2/1/95

char *color::cmove(int x, int y)// generate cursor movement command
{
	static char tmpstr[25];

	sprintf(tmpstr,"%c[%d;%dH",ESC,y,x);
	return(tmpstr);
};


// Method:     constructor
// Purpose:    initialize the object
// Input:      none
// Output:     none
// Author:     Greg Shaw
// Created:     1/29/95

color::color()
{
	int x;

	if (!inited)
	{
		inited = 1;
		default_foreground = def_foreground();
		default_background = def_background();
		default_attribute = def_attribute();
		sstr(normal());         // 'normal' attributes
		for (x=0; x<2; x++)
		{                       // location isn't used at this point
			location[x] = 0;
			s_location[x] = 0;
		}
		// check for valid tty
		if (isatty(fileno(stdout)))

			set_mode();
	}
};


// Method:     clean_len
// Purpose:    return the length of a string with the embedded chars ignored
// Input:      str - the string to look at
// Output:     the length (approximate) of the string w/o embedded chars
// Author:     Greg Shaw
// Created:        2/1/95

int color::clean_len(char *str)
{
	register char *c;           // string pointer
	register int num = 0;       // number of chars

	if (c = strchr(str,'\\'), c == NULL)
	{
		return(strlen(str));
	}
	c = str;
	while (*c != 0)             // go until end
	{
		if (*c++ != '\\')
		{
			num++;
		}
		else
		{
			switch(*c++)        // skip the appropriate number of chars
			{
				case 'f':       // foreground change
				case 'b':       // background change
					c++;
					break;
				case 'l':       // blink
				case 'o':       // bold strings
				case 'i':       // inverse
				case 'u':       // underline
				case 'd':       // 'dim'  (!bold)
				case 'n':       // back to 'normal' (defaults)
					break;
					// search for color strings
				case 'c':
					c += 2;
					break;
					// don't do movement yet - process in 2nd pass
				case 'a':       // absolute movement
					// NOTE: this may not be perfect,
					// but it's the best I can think of at
					// this point.
					// go up to comma
					while (*c != ',' && *c != 0)
						c++;
					c++;        // skip comma
					// now skip the numbers
					while (isdigit(*c) && *c != 0)
						c++;
					break;
				case 'j':       // right justified placement
					// go up to comma
					while (*c != ',' && *c != 0)
						c++;
					c++;        // skip comma
					// now skip the numbers
					while (isdigit(*c) && *c != 0)
						c++;
					break;
				default:        // some other string
					ap_log("Invalid ansi attribute found.");
					break;      // who cares?
			}
		}
	}
	return(num);
};


// Method: clear
// Purpose:    attempt to clear the user's screen
// Input:  none
// Output: a clear-screen string is sent to the user
// Author: Greg Shaw
// Created:    7/25/93

void color::clear_scr(void)
{
	char   clrstr[] =           // ctrl-l (new page)
	{
		0xc,0
	};

	char   ansi_clrstr[] =      // ansi clear screen
	{
		ESC,'[','2','J',ESC,'[','0',';','H',0

	};

	if (has_ansi() || (!strcmp(user.terminal(),"vt100")) || (!strcmp(user.terminal(),"xterm")))
	{
		sstr(ansi_clrstr);
	}
	else
		sstr(clrstr);
	sstr(normal());
};


// Method:     dim
// Purpose:    (re) set dim
// Input:      none
// Output:     the !bold attribute is sent
// Author:     Greg Shaw
// Created:     2/1/95

char *color::dim(void)
{
	attributes.dim  ^= 1;       // reset mode
	attributes.bold ^= 1;       // reset mode
	check_normal();             // reset 'normal' bit as necessary
	return(ansi_color_string(fore_c,back_c,attributes));
};


// Method: display_file
// Purpose:    display a file to the user using the system pager
// Input:  path - path and file name (absolute path only)
//         page - use paging
// Output: file is displayed or error returned.
// Author: Greg Shaw
// Created:    7/25/93

int color::display_file(char *path, char page)
{
	char   pagername[30];
	char   tmpstr[255];
	char   c;
	int    clen;
	FILE   *infile;


	clear_scr();
	if (page)
	{
		// add file name
		sprintf(tmpstr,"Viewing %s\n",path);
		mon_obj.send_monitor(tmpstr);
		sstrcr_c(QTOQUIT);
		if (strcpy(pagername,sys_pager()), pagername == NULL)
			return(-1);
		// get command name
		strcpy(tmpstr,pagername);
		strcat(tmpstr," ");     // add space
		strcat(tmpstr,path);    // add file name
		if (sysint(tmpstr,0,0) < 0)
		{
			ap_log("display_file: unable to execute sysint() call.");
			return(-1);
		}
		waitcr();
	}
	else
	{
		if (infile = bopen(path,"r"), infile == NULL)
			return(-1);
		else
			clen = 0;
		while (!feof(infile))
		{
			c = fgetc(infile);
			if (clen % 50 == 0 && clen > 0)
			{
				tmpstr[clen] = 0;
				sstr(tmpstr);
				clen = 0;
			}
			if (!feof(infile))
				tmpstr[clen++] = c;
		}
		if (clen > 0)
		{
			tmpstr[clen] = 0;
			sstr(tmpstr);
		}
		bclose(infile);
		waitcr();
	}
	return(0);
};


// Method:     foreground
// Purpose:    attempt to set the foreground color
// Input:      val - the color to set the foreground to:
//     1 - black
//     2 - red
//     3 - green
//     4 - yellow
//     5 - blue
//     6 - magenta
//         7 - cyan
//     8 - white
// Output:     if the terminal type is ansi, it sets the foreground color
//     to the appropriate color
// Author:     Greg Shaw
// Notes:  At this point, only those colors supported by ANSI are used.
//     (if true color is needed in the future, this function will
//     change radically)
// Created:    1/29/95

char *color::foreground(int val)
{
	char tmpstr[255];

	if (has_ansi())
	{                           // support only ansi at this point
		if (val < BLACK || val > WHITE)
		{
			sprintf(tmpstr,"color: invalid foreground color type (%d) %c found",val,val);
			ap_log(tmpstr);
			color_string[0] = 0;
			return(color_string);
		}
		fore_c = val;
		// send the string to set color attributes
		return(ansi_color_string(fore_c,back_c,attributes));
	}
	color_string[0] = 0;
	return(color_string);

};


// Method:     inverse
// Purpose:    (re) set inverse
// Input:      none
// Output:     the inverse code is sent.
// Author:     Greg Shaw
// Created:     1/29/95

char *color::inverse(void)
{
	attributes.inverse ^= 1;    // reset mode
	check_normal();             // reset 'normal' bit as necessary
	return(ansi_color_string(fore_c,back_c,attributes));
};


// Method:     has_ansi
// Purpose:    return non-zero for ansi capability
// Input:      none
// Output:     0 if not ansi termtype
// Author:     Greg Shaw
// Created:    1/29/95

int color::has_ansi(void)
{
	if (!strcmp(user.terminal(),"ansi"))
		return(1);
	else
		return(0);
};


// Method:     normal
// Purpose:    clear all attribute bits and set standard colors
// Input:      none
// Output:     the 'normal' code is sent
// Author:     Greg Shaw
// Created:     1/29/95

char *color::normal(void)
{
	// clear attributes
	memset(&attributes,0,sizeof(Attributes));
	switch(default_attribute)
	{
		case 1:                 // normal (dim)
			// set normal mode
			attributes.normal = 1;
			break;
		case 2:                 // bold
			attributes.bold = 1;
			break;
		case 3:                 // blink     (I HOPE you're kidding about this)
			attributes.blink = 1;
			break;
		case 4:                 // inverse
			attributes.inverse = 1;
			break;
		default:
			// set normal mode
			attributes.normal = 1;
	}
	fore_c = default_foreground;// get default foreground
	back_c = default_background;// set default background
	// and send to user
	return(ansi_color_string(fore_c,back_c,attributes));
};


// Method:     process
// Purpose:    digest a string for color information
// Input:      str - the string to convert
// Output:     string + color information if possible
// Author:     Greg Shaw
// Notes:  movement commands have to be processed after color commands
//     since the left justified command needs the length of the
//     rest of the string.
// Created:    1/29/95

char *color::process(char *str)
{
	char *c,*s,*as;
	char tmpstr[100];
	int  x,y;                   // movement coordinates
	static char newstring[MAX_COLOR_STRING];

	// check for trivial case - nothing embedded
	as = NULL;
	if (c = strchr(str,'\\'), c == NULL)
	{
		return(str);
	}
	newstring[0] = 0;           // start at 0
	c = str;                    // start at the beginning
	// search for embedded strings
	while (s = strchr(c,'\\'), s != NULL)
	{
		if (c != s)
		{
			if ((s-c-1+strlen(newstring)) < MAX_COLOR_STRING)
				strncat(newstring,c,s-c);
			else
			{
				ap_log("color.process(top): color string too long (>MAX_COLOR_STRING)");
				exit(0);        // get nasty
			}
		}
		switch (*(++s))         // what is it?
		{
			case 'f':           // foreground change
				as = foreground(*(++s)-'0');
				break;
			case 'b':           // background change
				as = background(*(++s)-'0');
				break;
			case 'l':           // blink
				as = blink();
				break;
			case 'o':           // bold strings
				as = bold();
				break;
			case 'i':           // inverse
				as = inverse();
				break;
			case 'u':           // underline
				as = underline();
				break;
			case 'd':           // 'dim'  (!bold)
				as = dim();
				break;
			case 'n':           // back to 'normal' (defaults)
				as = normal();
				break;
				// search for color strings
			case 'c':
				// get foreground string
				as = foreground(*(++s)-'0');
				strcat(newstring,as);
				// get background string
				as = background(*(++s)-'0');
				break;
				// don't do movement yet - process in 2nd pass
			case 'a':           // absolute movement
				// format: \a"num","num"
				sscanf(++s,"%d,%d%*s",&x,&y);
				as = cmove(x,y);
				// now let's skip the rest of this thang
				while (*s != ',' && *s != 0)
					s++;
				s++;            // skip comma
				// now skip the numbers
				while (isdigit(*c) && *s != 0)
					s++;
				break;
			case 'r':           // relative movement
				// format: \r"num","num"
				// don't do relative at this point.
				break;
			case 'j':           // right justified placement
				// format: \j"num","num"
				if (sscanf(++s,"%d,%d%*s",&x,&y) != 2)
				{
					ap_log("menu.process: right justified menu item incorrect");
					if (strlen(s)+15 < 100)
					{
						sprintf(tmpstr,"string was: %s",s);
						ap_log(tmpstr);
					}
					break;
				}
				// now let's skip the rest of this thang
				while (*s != ',' && *s != 0)
					s++;
				s++;            // skip comma
				// now skip the numbers
				while (isdigit(*s) && *s != 0)
					s++;
				as = rjmove(x,y,clean_len(s));
				--s;            // minor correction
				break;
			default:            // some other string
				#ifdef SLASH_KEYWORD
				sprintf(tmpstr,"color.process: Invalid substring: %c",*s);
				ap_log(tmpstr);
				#endif
				break;
		}
		// now add to the string
		if ((strlen(as)+strlen(newstring)) < MAX_COLOR_STRING)
			strcat(newstring,as);
		else
		{
			ap_log("color.process: color string too long (>MAX_COLOR_STRING)");
			exit(0);            // get nasty
		}
		c = ++s;
	}
	// add rest of string (with no codes)
	strcat(newstring,c);
	return(newstring);
}


// Method:     rjmove
// Purpose:    generate an ansi movement command taking length of
//     string into account
// Input:      x,y - location to move to
//     len - length of string to subtract from x coordinate
// Output:     an absolute ansi movement command is generated
// Author:     Greg Shaw
// Created:     2/1/95

// left justified movement
char *color::rjmove(int x, int y, int len)
{
	if (x-len > 0)
		return(cmove(x-len+2,y));
	else
		return(cmove(0,y));
};


// Method:     set_mode
// Purpose:    set the user's terminal mode, if applicable
// Input:      none
// Output:     if the terminal type is ansi, the mode is set to 80x25
// Author:     Greg Shaw
// Created:    1/29/95

void color::set_mode(void)
{
	char ansi_80x25[] =
	{
		ESC,'[','3','h',0
	};

	if (has_ansi())
	{
		sstr(ansi_80x25);
	}
};


// Method:     sstr_c
// Purpose:    digest a string for color information and send to user
// Input:      str - the string to convert
// Output:     string + color information if possible
// Author:     Greg Shaw
// Created:    1/29/95

int color::sstr_c(char *str)
{
	return(sstr(process(str)));
}


// Method:     sstrcr_c
// Purpose:    digest a string for color information and send to user
// Input:      str - the string to convert
// Output:     string + color information if possible
// Author:     Greg Shaw
// Created:    1/29/95

int color::sstrcr_c(char *str)
{
	return(sstrcr(process(str)));
}


// Method:     underline
// Purpose:    (re) set underline
// Input:      none
// Output:     the underline code is sent.
// Author:     Greg Shaw
// Created:     1/29/95

char *color::underline(void)
{
	if (!user.has_color())
	{
		// reset mode
		attributes.underline ^= 1;
		check_normal();         // reset 'normal' bit as necessary
		return (ansi_color_string(fore_c,back_c,attributes));
	}
	color_string[0] = 0;
	return(color_string);
};


// Method: wait
// Purpose:    prompt the user to hit 'return' to continue
// Input:  none
// Output: none
// Author: Greg Shaw
// Notes:  moved into color so that the continue prompt may be colorized
// Created:    7/25/93

void color::waitcr(void)
{
	time_t now,then;
	int    inactivity;
	char   c;

	time(&then);
	inactivity = inactivity_timeout();
	sstr_c(CONTINUE);
	while (c = gch(1), c != '\r' && c != '\n' && c != ' ')
	{
		time(&now);
		if ((now -then)/60 > inactivity)
			return;
	}
	cr();
};


#endif                          // _COLOR_C_





