/*
 * Copyright (c) 1984 through 2008, William LeFebvre
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 * 
 *     * Neither the name of William LeFebvre nor the names of other
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  Top users/processes display for Unix
 *  Version 3
 */

/*
 * This file handles color definitions and access for augmenting
 * the output with ansi color sequences.
 *
 * The definition of a color setting is as follows, separated by
 * colons:
 *
 * tag=minimum,maximum#code
 *
 * "tag" is the name of the value to display with color.
 *
 * "minimum" and "maximum" are positive integer values defining a range:
 * when the value is within this range it will be shown with the
 * specified color.  A missing value indicates that no check should be
 * made (i.e.: ",25" is n <= 25; "25,50" is 25 <= n <= 50; and "50,"
 * is 50 <= n).
 * 
 * "code" is the ansi sequence that defines the color to use with the
 * escape sequence "[m".  Semi-colons are allowed in this string to
 * combine attributes.
 */

#include "os.h"
#include "display.h"
#include "message.h"
#include "color.h"
#include "utils.h"

typedef struct color_entry {
    char *tag;
    int min;
    int max;
    char color;
    struct color_entry *next;
    struct color_entry *tagnext;
} color_entry;

static color_entry *entries = NULL;

static color_entry **bytag = NULL;
static char **bytag_names = NULL;
static int totaltags = 0;
static int tagcnt = 0;
static int color_off = 0;

static char **color_ansi = NULL;
static int num_color_ansi = 0;
static int max_color_ansi = 0;

static int
color_slot(char *str)

{
    int i;

    for (i = 0; i < num_color_ansi; i++)
    {
	if (strcmp(color_ansi[i], str) == 0)
	{
	    return i;
	}
    }

    /* need a new slot */
    if (num_color_ansi >= max_color_ansi)
    {
	max_color_ansi += COLOR_ANSI_SLOTS;
	color_ansi = (char **)realloc(color_ansi, max_color_ansi * sizeof(char *));
    }
    color_ansi[num_color_ansi] = strdup(str);
    return num_color_ansi++;
}

/*
 * int color_env_parse(char *env)
 *
 * Parse a color specification "env" (such as one found in the environment) and
 * add them to the list of entries.  Always returns 0.  Should only be called
 * once.
 */

int
color_env_parse(char *env)

{
    char *p;
    char *min;
    char *max;
    char *str;
    int len;
    color_entry *ce;

    /* initialization */
    color_ansi = (char **)malloc(COLOR_ANSI_SLOTS * sizeof(char *));
    max_color_ansi = COLOR_ANSI_SLOTS;

    /* color slot 0 is always "0" */
    color_slot("0");

    if (env != NULL)
    {
	p = strtok(env, ":");
	while (p != NULL)
	{
	    if ((min = strchr(p, '=')) != NULL &&
		(max = strchr(min, ',')) != NULL &&
		(str = strchr(max, '#')) != NULL)
	    {
		ce = (color_entry *)malloc(sizeof(color_entry));
		len = min - p;
		ce->tag = (char *)malloc(len + 1);
		strncpy(ce->tag, p, len);
		ce->tag[len] = '\0';
		ce->min = atoi(++min);
		ce->max = atoi(++max);
		ce->color = color_slot(++str);
		ce->next = entries;
		entries = ce;
	    }
	    else
	    {
		if (min != NULL)
		{
		    len = min - p;
		}
		else
		{
		    len = strlen(p);
		}
		message_error(" %.*s: bad color entry", len, p);
	    }
	    p = strtok(NULL, ":");
	}
    }
    return 0;
}

/*
 * int color_tag(char *tag)
 *
 * Declare "tag" as a color tag.  Return a tag index to use when testing
 * a value against the tests for this tag.  Should not be called before
 * color_env_parse.
 */

int
color_tag(char *tag)

{
    color_entry *entryp;
    color_entry *tp;

    /* check for absurd arguments */
    if (tag == NULL || *tag == '\0')
    {
	return -1;
    }

    dprintf("color_tag(%s)\n", tag);

    /* initial allocation */
    if (bytag == NULL)
    {
	totaltags = 10;
	bytag = (color_entry **)malloc(totaltags * sizeof(color_entry *));
	bytag_names = (char **)malloc(totaltags * sizeof(char *));
    }

    /* if we dont have enough room then reallocate */
    if (tagcnt >= totaltags)
    {
	totaltags *= 2;
	bytag = (color_entry **)realloc(bytag, totaltags * sizeof(color_entry *));
	bytag_names = (char **)realloc(bytag_names, totaltags * sizeof(char *));
    }

    /* initialize scan */
    entryp = entries;
    tp = NULL;

    /* look for tag in the list of entries */
    while (entryp != NULL)
    {
	if (strcmp(entryp->tag, tag) == 0)
	{
	    entryp->tagnext = tp;
	    tp = entryp;
	}
	entryp = entryp->next;
    }

    /* we track names in the array bytag */
    bytag[tagcnt] = tp;
    bytag_names[tagcnt] = strdup(tag);

    /* return this index number as a reference */
    dprintf("color_tag: returns %d\n", tagcnt);
    return (tagcnt++);
}

/*
 * int color_test(int tagidx, int value)
 *
 * Test "value" against tests for tag "tagidx", a number previously returned
 * by color_tag.  Return the correct color number to use when highlighting.
 * If there is no match, return 0 (color 0).
 */

int
color_test(int tagidx, int value)

{
    color_entry *ce;

    /* sanity check */
    if (tagidx < 0 || tagidx >= tagcnt || color_off)
    {
	return 0;
    }

    ce = bytag[tagidx];

    while (ce != NULL)
    {
	if ((!ce->min || ce->min <= value) &&
	    (!ce->max || ce->max >= value))
	{
	    return ce->color;
	}
	ce = ce->tagnext;
    }

    return 0;
}

/*
 * char *color_setstr(int color)
 *
 * Return ANSI string to set the terminal for color number "color".
 */

char *
color_setstr(int color)

{
    static char v[32];

    v[0] = '\0';
    if (color >= 0 && color < num_color_ansi)
    {
	snprintf(v, sizeof(v), "\033[%sm", color_ansi[color]);
    }
    return v;
}

void
color_dump(FILE *f)

{
    color_entry *ep;
    int i;
    int col;
    int len;

    fputs("These color tags are available:", f);
    col = 81;
    for (i = 0; i < tagcnt; i++)
    {
	len = strlen(bytag_names[i]) + 1;
	if (len + col > 79)
	{
	    fputs("\n  ", f);
	    col = 2;
	}
	fprintf(f, " %s", bytag_names[i]);
	col += len;
    }

    fputs("\n\nTop color settings:\n", f);

    for (i = 0; i < tagcnt; i++)
    {
	ep = bytag[i];
	while (ep != NULL)
	{
	    fprintf(f, "   %s (%d-", ep->tag, ep->min);
	    if (ep->max != 0)
	    {
		fprintf(f, "%d", ep->max);
	    }
	    fprintf(f, "): ansi color %s, %sSample Text",
		    color_ansi[(int)ep->color],
		    color_setstr(ep->color));
	    fprintf(f, "%s\n", color_setstr(0));
	    ep = ep -> tagnext;
	}
    }
}

void
color_debug(FILE *f)

{
    color_entry *ep;
    int i;

    fprintf(f, "color debug dump\n");
    ep = entries;
    while (ep != NULL)
    {
	fprintf(f, "%s(%d,%d): slot %d, ansi %s, %sSample Text",
		ep->tag, ep->min, ep->max, ep->color, color_ansi[(int)ep->color],
	       color_setstr(ep->color));
	fprintf(f, "%s\n", color_setstr(0));
	ep = ep -> next;
    }

    fprintf(f, "\ntags:");
    for (i = 0; i < tagcnt; i++)
    {
	fprintf(f, " %s", bytag_names[i]);
    }
    fprintf(f, "\n");
}

int
color_activate(int i)

{
    if (i == -1)
    {
	color_off = !color_off;
    }
    else
    {
	color_off = !i;
    }
    return color_off;
}
