/*
 * Configurable ps-like program.
 * Color support routines.
 *
 * Copyright (c) 2010 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#include <string.h>
#include <memory.h>

#include "ips.h"
#include "expr.h"


/*
 * A pair of color names specifying a foreground and background and
 * an associated set of flags which modify the appearance of the text.
 * If either string is empty then that means use the default
 * foreground or background color.
 */
typedef struct
{
	const char *	foreground;
	const char *	background;
	int		flags;
}
ColorData;


/*
 * The definition of a color to be applied to rows which meet a
 * specified condition.
 */
typedef	struct
{
	int	colorId;		/* color id for row */
	TREE	tree;			/* condition required for coloring */
}
RowColor;


/*
 * The table of color data structures.
 * The table is indexed by the color id, with the first entry being
 * that of the default color id.
 */
static	int		colorCount;
static	ColorData	colorTable[MAX_COLORS];


/*
 * The table of row coloring conditions.
 */
static	int		rowColorCount;
static	RowColor	rowColorTable[MAX_ROW_COLORS];


/*
 * Initialize the color table.
 * This defines the DEFAULT_COLOR_ID entry which has the default
 * foreground and background colors and no color flags.
 */
void
InitializeColors(void)
{
	colorTable[0].foreground = "";
	colorTable[0].background = "";
	colorTable[0].flags = COLOR_FLAG_NONE;

	colorCount = 1;
}


/*
 * Allocate a color id for the specified pair of colors and flags
 * (while returning the same id if the same values are specified again).
 * The string contains a pair of color names separated with a slash.
 * Either name can be empty or the special "default" value to indicate
 * the default foreground or background color.  If there is no slash
 * then only the foreground color is specified.  If a second slash is
 * given then a set of color flag characters are parsed.  Returns the
 * color id on success or BAD_COLOR_ID on an failure.
 */
int
AllocateColor(const char * color)
{
	char *		dupColor;
	char *		slash;
	const char *	foreground;
	const char *	background;
	const char *	flagsString;
	ColorData *	pair;
	int		colorFlags;
	int		colorId;

	/*
	 * Copy the name and split it name into its foreground,
	 * background, and flag parts which are separated by slashes.
	 */
	dupColor = strdup(color);

	if (dupColor == 0)
		return BAD_COLOR_ID;

	foreground = dupColor;
	background = "";
	flagsString = "";

	slash = strchr(dupColor, '/');

	if (slash)
	{
		*slash++ = '\0';
		background = slash;
	}

	if (slash)
		slash = strchr(slash, '/');

	if (slash)
	{
		*slash++ = '\0';
		flagsString = slash;
	}

	/*
	 * Parse the flag characters into bits.
	 */
	colorFlags = COLOR_FLAG_NONE;

	while (*flagsString)
	{
		switch (*flagsString++)
		{
			case 'u':
				colorFlags |= COLOR_FLAG_UNDERLINE;
				break;

			case 'b':
				colorFlags |= COLOR_FLAG_BOLD;
				break;

			default:
				return BAD_COLOR_ID;
		}
	}

	/*
	 * Check for the default name and change it to blank.
	 */
	if (strcmp(foreground, DEFAULT_COLOR_NAME) == 0)
		foreground = "";

	if (strcmp(background, DEFAULT_COLOR_NAME) == 0)
		background = "";

	/*
	 * Search the color table for an existing entry and use that.
	 */
	for (colorId = 0; colorId < colorCount; colorId++)
	{
		pair = &colorTable[colorId];

		if ((strcmp(pair->foreground, foreground) == 0) &&
			(strcmp(pair->background, background) == 0) &&
			(pair->flags == colorFlags))
		{
			return colorId;
		}
	}

	/*
	 * The color pair is new.
	 * Allocate a new entry and return its color id.
	 */
	if (colorCount >= MAX_COLORS)
	{
		free(dupColor);

		return BAD_COLOR_ID;
	}

	pair = &colorTable[colorCount];

	pair->foreground = foreground;
	pair->background = background;
	pair->flags = colorFlags;

	return colorCount++;
}


/*
 * Define all of the colors that have been allocated.
 * This is called within the opening code of the display.
 */
BOOL
DefineColors(void)
{
	int			colorId;
	const ColorData *	colorData;

	for (colorId = 0; colorId < colorCount; colorId++)
	{
		colorData = &colorTable[colorId];

		if (!DpyDefineColor(colorId, colorData->foreground,
			colorData->background, colorData->flags))
		{
			return FALSE;
		}
	}

	return TRUE;
}


/*
 * Clear the row color conditions.
 * This can lose memory but that happens only on startup.
 */
void
ClearRowColorConditions(void)
{
	rowColorCount = 0;
}


/*
 * Allocate and parse a new row coloring entry for the specified condition.
 * The color is a foreground/background/flags value separated by an optional slash.
 * Returns TRUE on success.
 */
BOOL
ParseRowColorCondition(const char * color, const char * condition)
{
	RowColor *	rowColor;

	if (rowColorCount >= MAX_ROW_COLORS)
		return FALSE;

	rowColor = &rowColorTable[rowColorCount];

	rowColor->colorId = AllocateColor(color);

	if (rowColor->colorId == BAD_COLOR_ID)
		return FALSE;

	if (!ParseTree(&rowColor->tree, condition, 0))
		return FALSE;

	rowColorCount++;

	return TRUE;
}


/*
 * Return the use flags for the row color conditions.
 */
USEFLAG
GetRowColorUseFlags(void)
{
	USEFLAG		useFlags = USE_NONE;
	int		index;

	for (index = 0; index < rowColorCount; index++)
	{
		useFlags |= GetNodeUseFlags(rowColorTable[index].tree.root);
	}

	return useFlags;
}


/*
 * Evaluate and return the color id to be used for a process row.
 * The default color is returned if there are no conditions.
 */
int
EvaluateRowColor(const PROC * proc)
{
	int		colorId = DEFAULT_COLOR_ID;
	int		index;
	BOOL		isWanted;
	RowColor *	rowColor;
	VALUE		value;

	for (index = 0; index < rowColorCount; index++)
	{
		isWanted = FALSE;

		rowColor = &rowColorTable[index];

		rowColor->tree.proc = proc;

		value = EvaluateNode(&rowColor->tree, rowColor->tree.root);

		if ((value.type == VALUE_NUMBER) || (value.type == VALUE_BOOLEAN))
		{
			isWanted = (value.intVal != 0);
		}
		else if (value.type == VALUE_STRING)
		{
			isWanted = (*value.strVal != '\0');
		}

		if (isWanted)
			colorId = rowColor->colorId;
	}

	return colorId;
}

/* END CODE */
