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

    palette.c

    Palette handling functions.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

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

#include "palette.h"



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* object to track dirty states */
typedef struct _dirty_state dirty_state;
struct _dirty_state
{
	UINT32 *		dirty;						/* bitmap of dirty entries */
	UINT32			mindirty;					/* minimum dirty entry */
	UINT32			maxdirty;					/* minimum dirty entry */
};


/* a single palette client */
struct _palette_client
{
	palette_client *next;						/* pointer to next client */
	palette_t *		palette;					/* reference to the palette */
	dirty_state		live;						/* live dirty state */
	dirty_state		previous;					/* previous dirty state */
};


/* a palette object */
struct _palette_t
{
	UINT32			refcount;					/* reference count on the palette */
	UINT32			numcolors;					/* number of colors in the palette */
	UINT32			numgroups;					/* number of groups in the palette */

	rgb_t *			entry_color;				/* array of raw colors */
	float *			entry_contrast;				/* contrast value for each entry */
	rgb_t *			adjusted_color;				/* array of adjusted colors */
	rgb_t *			adjusted_rgb15;				/* array of adjusted colors as RGB15 */

	float *			group_bright;				/* brightness value for each group */
	float *			group_contrast;				/* contrast value for each group */

	palette_client *client_list;				/* list of clients for this palette */
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

static void internal_palette_free(palette_t *palette);
static void update_adjusted_color(palette_t *palette, UINT32 group, UINT32 index);



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    adjust_palette_entry - adjust a palette
    entry for brightness
-------------------------------------------------*/

INLINE rgb_t adjust_palette_entry(rgb_t entry, float brightness, float contrast)
{
	int r = rgb_clamp((float)RGB_RED(entry) * contrast + brightness);
	int g = rgb_clamp((float)RGB_GREEN(entry) * contrast + brightness);
	int b = rgb_clamp((float)RGB_BLUE(entry) * contrast + brightness);
	return MAKE_RGB(r,g,b);
}



/***************************************************************************
    PALETTE ALLOCATION
***************************************************************************/

/*-------------------------------------------------
    palette_alloc - allocate a new palette object
    and take a single reference on it
-------------------------------------------------*/

palette_t *palette_alloc(UINT32 numcolors, UINT32 numgroups)
{
	palette_t *palette;
	UINT32 index;

	/* allocate memory */
	palette = malloc(sizeof(*palette));
	if (palette == NULL)
		goto error;
	memset(palette, 0, sizeof(*palette));

	/* allocate an array of palette entries and individual contrasts for each */
	palette->entry_color = malloc(sizeof(*palette->entry_color) * numcolors);
	palette->entry_contrast = malloc(sizeof(*palette->entry_contrast) * numcolors);
	if (palette->entry_color == NULL || palette->entry_contrast == NULL)
		goto error;

	/* initialize the entries */
	for (index = 0; index < numcolors; index++)
	{
		palette->entry_color[index] = RGB_BLACK;
		palette->entry_contrast[index] = 1.0f;
	}

	/* allocate an array of brightness and contrast for each group */
	palette->group_bright = malloc(sizeof(*palette->group_bright) * numgroups);
	palette->group_contrast = malloc(sizeof(*palette->group_contrast) * numgroups);
	if (palette->group_bright == NULL || palette->group_contrast == NULL)
		goto error;

	/* initialize the entries */
	for (index = 0; index < numgroups; index++)
	{
		palette->group_bright[index] = 0.0f;
		palette->group_contrast[index] = 1.0f;
	}

	/* allocate arrays for the adjusted colors */
	palette->adjusted_color = malloc(sizeof(*palette->adjusted_color) * (numcolors * numgroups + 2));
	palette->adjusted_rgb15 = malloc(sizeof(*palette->adjusted_rgb15) * (numcolors * numgroups + 2));
	if (palette->adjusted_color == NULL || palette->adjusted_rgb15 == NULL)
		goto error;

	/* initialize the arrays */
	for (index = 0; index < numcolors * numgroups; index++)
	{
		palette->adjusted_color[index] = RGB_BLACK;
		palette->adjusted_rgb15[index] = rgb_to_rgb15(RGB_BLACK);
	}

	/* add black and white as the last two colors */
	palette->adjusted_color[index] = RGB_BLACK;
	palette->adjusted_rgb15[index++] = rgb_to_rgb15(RGB_BLACK);
	palette->adjusted_color[index] = RGB_WHITE;
	palette->adjusted_rgb15[index++] = rgb_to_rgb15(RGB_WHITE);

	/* initialize the remainder of the structure */
	palette->refcount = 1;
	palette->numcolors = numcolors;
	palette->numgroups = numgroups;
	return palette;

error:
	if (palette != NULL)
		internal_palette_free(palette);

	return NULL;
}


/*-------------------------------------------------
    palette_ref - reference a palette object,
    incrementing its reference count
-------------------------------------------------*/

void palette_ref(palette_t *palette)
{
	palette->refcount++;
}


/*-------------------------------------------------
    palette_deref - dereference a palette object;
    if the reference count goes to 0, it is freed
-------------------------------------------------*/

void palette_deref(palette_t *palette)
{
	/* if the reference count goes to 0, free */
	if (--palette->refcount == 0)
		internal_palette_free(palette);
}



/***************************************************************************
    PALETTE INFORMATION
***************************************************************************/

/*-------------------------------------------------
    palette_get_num_colors - return the number of
    colors allocated in the palette
-------------------------------------------------*/

int palette_get_num_colors(palette_t *palette)
{
	return palette->numcolors;
}


/*-------------------------------------------------
    palette_get_num_groups - return the number of
    groups managed by the palette
-------------------------------------------------*/

int palette_get_num_groups(palette_t *palette)
{
	return palette->numgroups;
}


/*-------------------------------------------------
    palette_get_black_entry - return the index of
    the black entry
-------------------------------------------------*/

UINT32 palette_get_black_entry(palette_t *palette)
{
	return palette->numcolors * palette->numgroups + 0;
}


/*-------------------------------------------------
    palette_get_white_entry - return the index of
    the white entry
-------------------------------------------------*/

UINT32 palette_get_white_entry(palette_t *palette)
{
	return palette->numcolors * palette->numgroups + 1;
}



/***************************************************************************
    PALETTE CLIENTS
***************************************************************************/

/*-------------------------------------------------
    palette_client_alloc - add a new client to a
    palette
-------------------------------------------------*/

palette_client *palette_client_alloc(palette_t *palette)
{
	UINT32 total_colors = palette->numcolors * palette->numgroups;
	UINT32 dirty_dwords = (total_colors + 31) / 32;
	palette_client *client;

	/* allocate memory for the client */
	client = malloc(sizeof(*client));
	if (client == NULL)
		goto error;
	memset(client, 0, sizeof(*client));

	/* allocate dirty lists */
	client->live.dirty = malloc(dirty_dwords * sizeof(UINT32));
	client->previous.dirty = malloc(dirty_dwords * sizeof(UINT32));
	if (client->live.dirty == NULL || client->previous.dirty == NULL)
		goto error;

	/* mark everything dirty to start with */
	memset(client->live.dirty, 0xff, dirty_dwords * sizeof(UINT32));
	memset(client->previous.dirty, 0xff, dirty_dwords * sizeof(UINT32));
	client->live.dirty[dirty_dwords - 1] &= (1 << (total_colors % 32)) - 1;
	client->previous.dirty[dirty_dwords - 1] &= (1 << (total_colors % 32)) - 1;

	/* initialize the rest of the structure and add a reference to a palette */
	client->palette = palette;
	palette_ref(palette);
	client->live.mindirty = 0;
	client->live.maxdirty = total_colors - 1;

	/* now add us to the list of clients */
	client->next = palette->client_list;
	palette->client_list = client;
	return client;

error:
	if (client != NULL)
	{
		if (client->live.dirty != NULL)
			free(client->live.dirty);
		if (client->previous.dirty != NULL)
			free(client->previous.dirty);
		free(client);
	}
	return NULL;
}


/*-------------------------------------------------
    palette_client_free - remove a client from a
    palette
-------------------------------------------------*/

void palette_client_free(palette_client *client)
{
	palette_client **curptr;

	/* first locate and remove ourself from the palette's list */
	for (curptr = &client->palette->client_list; *curptr != NULL; curptr = &(*curptr)->next)
		if (*curptr == client)
		{
			*curptr = client->next;
			break;
		}

	/* now deref the palette */
	palette_deref(client->palette);

	/* free our data */
	if (client->live.dirty != NULL)
		free(client->live.dirty);
	if (client->previous.dirty != NULL)
		free(client->previous.dirty);
	free(client);
}


/*-------------------------------------------------
    palette_client_get_palette - return a pointer
    to the palette for this client
-------------------------------------------------*/

palette_t *palette_client_get_palette(palette_client *client)
{
	return client->palette;
}


/*-------------------------------------------------
    palette_client_get_dirty_list - atomically get
    the current dirty list for a client
-------------------------------------------------*/

const UINT32 *palette_client_get_dirty_list(palette_client *client, UINT32 *mindirty, UINT32 *maxdirty)
{
	dirty_state temp;

	/* fill in the mindirty/maxdirty */
	if (mindirty != NULL)
		*mindirty = client->live.mindirty;
	if (maxdirty != NULL)
		*maxdirty = client->live.maxdirty;

	/* if nothing to report, report nothing and don't swap */
	if (client->live.mindirty > client->live.maxdirty)
		return NULL;

	/* swap the live and previous lists */
	temp = client->live;
	client->live = client->previous;
	client->previous = temp;

	/* erase relevant entries in the new live one */
	if (client->live.mindirty <= client->live.maxdirty)
		memset(client->live.dirty, client->live.mindirty / 8, (client->live.maxdirty / 8) + 1 - (client->live.mindirty / 8));
	client->live.mindirty = client->palette->numcolors * client->palette->numgroups;
	client->live.maxdirty = 0;

	/* return a pointer to the previous table */
	return client->previous.dirty;
}



/***************************************************************************
    PALETTE COLOR MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    palette_entry_set_color - set the raw RGB
    color for a given palette index
-------------------------------------------------*/

void palette_entry_set_color(palette_t *palette, UINT32 index, rgb_t rgb)
{
	int groupnum;

	/* if out of range, or unchanged, ignore */
	if (index >= palette->numcolors || palette->entry_color[index] == rgb)
		return;

	/* set the color */
	palette->entry_color[index] = rgb;

	/* update across all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_entry_get_color - return the raw RGB
    color for a given palette index
-------------------------------------------------*/

rgb_t palette_entry_get_color(palette_t *palette, UINT32 index)
{
	return (index < palette->numcolors) ? palette->entry_color[index] : RGB_BLACK;
}


/*-------------------------------------------------
    palette_entry_get_adjusted_color - return the
    adjusted RGB color (after all adjustments) for
    a given palette index
-------------------------------------------------*/

rgb_t palette_entry_get_adjusted_color(palette_t *palette, UINT32 index)
{
	return (index < palette->numcolors * palette->numgroups) ? palette->adjusted_color[index] : RGB_BLACK;
}


/*-------------------------------------------------
    palette_entry_list_raw - return the entire
    palette as an array of raw RGB values
-------------------------------------------------*/

const rgb_t *palette_entry_list_raw(palette_t *palette)
{
	return palette->entry_color;
}


/*-------------------------------------------------
    palette_entry_list_adjusted - return the
    entire palette as an array of adjusted RGB
    values
-------------------------------------------------*/

const rgb_t *palette_entry_list_adjusted(palette_t *palette)
{
	return palette->adjusted_color;
}


/*-------------------------------------------------
    palette_entry_list_adjusted_rgb15 - return the
    entire palette as an array of adjusted RGB-15
    values
-------------------------------------------------*/

const rgb_t *palette_entry_list_adjusted_rgb15(palette_t *palette)
{
	return palette->adjusted_rgb15;
}



/***************************************************************************
    PALETTE ADJUSTMENTS
***************************************************************************/

/*-------------------------------------------------
    palette_entry_set_contrast - set the contrast
    adjustment for a single palette index
-------------------------------------------------*/

void palette_entry_set_contrast(palette_t *palette, UINT32 index, float contrast)
{
	int groupnum;

	/* if out of range, or unchanged, ignore */
	if (index >= palette->numcolors || palette->entry_contrast[index] == contrast)
		return;

	/* set the contrast */
	palette->entry_contrast[index] = contrast;

	/* update across all groups */
	for (groupnum = 0; groupnum < palette->numgroups; groupnum++)
		update_adjusted_color(palette, groupnum, index);
}


/*-------------------------------------------------
    palette_entry_get_contrast - return the
    contrast adjustment for a single palette index
-------------------------------------------------*/

float palette_entry_get_contrast(palette_t *palette, UINT32 index)
{
	return (index < palette->numcolors) ? palette->entry_contrast[index] : 1.0f;
}


/*-------------------------------------------------
    palette_group_set_brightness - configure
    overall brightness for a palette group
-------------------------------------------------*/

void palette_group_set_brightness(palette_t *palette, UINT32 group, float brightness)
{
	int index;

	/* if out of range, or unchanged, ignore */
	if (group >= palette->numgroups || palette->group_bright[group] == brightness)
		return;

	/* set the contrast */
	palette->group_bright[group] = brightness;

	/* update across all colors */
	for (index = 0; index < palette->numcolors; index++)
		update_adjusted_color(palette, group, index);
}


/*-------------------------------------------------
    palette_group_set_contrast - configure
    overall contrast for a palette group
-------------------------------------------------*/

void palette_group_set_contrast(palette_t *palette, UINT32 group, float contrast)
{
	int index;

	/* if out of range, or unchanged, ignore */
	if (group >= palette->numgroups || palette->group_contrast[group] == contrast)
		return;

	/* set the contrast */
	palette->group_contrast[group] = contrast;

	/* update across all colors */
	for (index = 0; index < palette->numcolors; index++)
		update_adjusted_color(palette, group, index);
}



/***************************************************************************
    PALETTE UTILITIES
***************************************************************************/

/*-------------------------------------------------
    palette_normalize_range - normalize a range
    of palette entries
-------------------------------------------------*/

void palette_normalize_range(palette_t *palette, UINT32 start, UINT32 end, int lum_min, int lum_max)
{
	UINT32 ymin = 1000 * 255, ymax = 0;
	UINT32 tmin, tmax;
	UINT32 index;

	/* clamp within range */
	start = MAX(start, 0);
	end = MIN(end, palette->numcolors - 1);

	/* find the minimum and maximum brightness of all the colors in the range */
	for (index = start; index <= end; index++)
	{
		rgb_t rgb = palette->entry_color[index];
		UINT32 y = 299 * RGB_RED(rgb) + 587 * RGB_GREEN(rgb) + 114 * RGB_BLUE(rgb);
		ymin = MIN(ymin, y);
		ymax = MAX(ymax, y);
	}

	/* determine target minimum/maximum */
	tmin = (lum_min < 0) ? ((ymin + 500) / 1000) : lum_min;
	tmax = (lum_max < 0) ? ((ymax + 500) / 1000) : lum_max;

	/* now normalize the palette */
	for (index = start; index <= end; index++)
	{
		rgb_t rgb = palette->entry_color[index];
		UINT32 y = 299 * RGB_RED(rgb) + 587 * RGB_GREEN(rgb) + 114 * RGB_BLUE(rgb);
		UINT32 target = tmin + ((y - ymin) * (tmax - tmin + 1)) / (ymax - ymin);
		UINT8 r = (y == 0) ? 0 : rgb_clamp(RGB_RED(rgb) * 1000 * target / y);
		UINT8 g = (y == 0) ? 0 : rgb_clamp(RGB_GREEN(rgb) * 1000 * target / y);
		UINT8 b = (y == 0) ? 0 : rgb_clamp(RGB_BLUE(rgb) * 1000 * target / y);
		palette_entry_set_color(palette, index, MAKE_RGB(r, g, b));
	}
}



/***************************************************************************
    INTERNAL ROUTINES
***************************************************************************/

/*-------------------------------------------------
    internal_palette_free - free all allocations
    from a palette
-------------------------------------------------*/

static void internal_palette_free(palette_t *palette)
{
	/* free per-color data */
	if (palette->entry_color != NULL)
		free(palette->entry_color);
	if (palette->entry_contrast != NULL)
		free(palette->entry_contrast);

	/* free per-group data */
	if (palette->group_bright != NULL)
		free(palette->group_bright);
	if (palette->group_contrast != NULL)
		free(palette->group_contrast);

	/* free adjusted data */
	if (palette->adjusted_color != NULL)
		free(palette->adjusted_color);
	if (palette->adjusted_rgb15 != NULL)
		free(palette->adjusted_rgb15);

	/* and the palette itself */
	free(palette);
}


/*-------------------------------------------------
    update_adjusted_color - update a color index
    by group and index pair
-------------------------------------------------*/

static void update_adjusted_color(palette_t *palette, UINT32 group, UINT32 index)
{
	UINT32 finalindex = group * palette->numcolors + index;
	palette_client *client;
	rgb_t adjusted;

	/* compute the adjusted value */
	adjusted = adjust_palette_entry(palette->entry_color[index], palette->group_bright[group], palette->group_contrast[group] * palette->entry_contrast[index]);

	/* if not different, ignore */
	if (palette->adjusted_color[finalindex] == adjusted)
		return;

	/* otherwise, modify the adjusted color array */
	palette->adjusted_color[finalindex] = adjusted;
	palette->adjusted_rgb15[finalindex] = rgb_to_rgb15(adjusted);

	/* mark dirty in all clients */
	for (client = palette->client_list; client != NULL; client = client->next)
	{
		client->live.dirty[finalindex / 32] |= 1 << (finalindex % 32);
		client->live.mindirty = MIN(client->live.mindirty, finalindex);
		client->live.maxdirty = MAX(client->live.maxdirty, finalindex);
	}
}
