// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    dipalette.cpp

    Device palette interface.

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

#include "emu.h"
#include "screen.h"

#define VERBOSE 0


//**************************************************************************
//  DEVICE INTERFACE MANAGEMENT
//**************************************************************************

//-------------------------------------------------
//  device_palette_interface - constructor
//-------------------------------------------------

device_palette_interface::device_palette_interface(const machine_config &mconfig, device_t &device)
	: device_interface(device, "palette"),
		m_palette(nullptr),
		m_pens(nullptr),
		m_format(BITMAP_FORMAT_RGB32),
		m_shadow_table(nullptr),
		m_shadow_group(0),
		m_hilight_group(0),
		m_white_pen(0),
		m_black_pen(0)
{
}


//-------------------------------------------------
//  interface_validity_check - validation for a
//  device after the configuration has been
//  constructed
//-------------------------------------------------

void device_palette_interface::interface_validity_check(validity_checker &valid) const
{
	// this info must be available before the device has started
	if (palette_entries() == 0)
		osd_printf_error("Palette has no entries specified\n");
}


//-------------------------------------------------
//  interface_pre_start - work to be done prior to
//  actually starting a device
//-------------------------------------------------

void device_palette_interface::interface_pre_start()
{
	// allocate the palette
	u32 numentries = palette_entries();
	allocate_palette(numentries);
	allocate_color_tables();
	allocate_shadow_tables();

	// allocate indirection tables
	int indirect_colors = palette_indirect_entries();
	if (indirect_colors > 0)
	{
		m_indirect_colors.resize(indirect_colors);
		for (int color = 0; color < indirect_colors; color++)
		{
			// alpha = 0 ensures change is detected the first time set_indirect_color() is called
			m_indirect_colors[color] = rgb_t::transparent();
		}

		m_indirect_pens.resize(numentries);
		for (int pen = 0; pen < numentries; pen++)
			m_indirect_pens[pen] = pen % indirect_colors;
	}
}


//-------------------------------------------------
//  interface_post_start - work to be done after
//  actually starting a device
//-------------------------------------------------

void device_palette_interface::interface_post_start()
{
	// set up save/restore of the palette
	m_save_pen.resize(m_palette->num_colors());
	m_save_contrast.resize(m_palette->num_colors());
	device().save_item(NAME(m_save_pen));
	device().save_item(NAME(m_save_contrast));

	// save indirection tables if we have them
	if (m_indirect_colors.size() > 0)
	{
		device().save_item(NAME(m_indirect_colors));
		device().save_item(NAME(m_indirect_pens));
	}
}


//-------------------------------------------------
//  interface_pre_save - prepare the save arrays
//  for saving
//-------------------------------------------------

void device_palette_interface::interface_pre_save()
{
	// fill the save arrays with updated pen and brightness information
	int numcolors = m_palette->num_colors();
	for (int index = 0; index < numcolors; index++)
	{
		m_save_pen[index] = pen_color(index);
		m_save_contrast[index] = pen_contrast(index);
	}
}


//-------------------------------------------------
//  interface_post_load - called after restore to
//  actually update the palette
//-------------------------------------------------

void device_palette_interface::interface_post_load()
{
	// reset the pen and brightness for each entry
	int numcolors = m_palette->num_colors();
	for (int index = 0; index < numcolors; index++)
	{
		set_pen_color(index, m_save_pen[index]);
		set_pen_contrast(index, m_save_contrast[index]);
	}
}


//-------------------------------------------------
//  interface_post_stop - final cleanup
//-------------------------------------------------

void device_palette_interface::interface_post_stop()
{
	// dereference the palette
	if (m_palette != nullptr)
		m_palette->deref();
}


//**************************************************************************
//  INDIRECTION (AKA COLORTABLES)
//**************************************************************************

//-------------------------------------------------
//  set_indirect_color - set an indirect color
//-------------------------------------------------

void device_palette_interface::set_indirect_color(int index, rgb_t rgb)
{
	// make sure we are in range
	assert(index < m_indirect_colors.size());

	// alpha doesn't matter
	rgb.set_a(255);

	// update if it has changed
	if (m_indirect_colors[index] != rgb)
	{
		m_indirect_colors[index] = rgb;

		// update the palette for any colortable entries that reference it
		for (u32 pen = 0; pen < m_indirect_pens.size(); pen++)
			if (m_indirect_pens[pen] == index)
				m_palette->entry_set_color(pen, rgb);
	}
}


//-------------------------------------------------
//  set_pen_indirect - set an indirect pen index
//-------------------------------------------------

void device_palette_interface::set_pen_indirect(pen_t pen, indirect_pen_t index)
{
	// make sure we are in range
	assert(pen < entries() && index < indirect_entries());

	m_indirect_pens[pen] = index;

	m_palette->entry_set_color(pen, m_indirect_colors[index]);
}


//-------------------------------------------------
//  transpen_mask - return a mask of pens that
//  whose indirect values match the given
//  transcolor
//-------------------------------------------------

u32 device_palette_interface::transpen_mask(gfx_element &gfx, u32 color, indirect_pen_t transcolor) const
{
	u32 entry = gfx.colorbase() + (color % gfx.colors()) * gfx.granularity();

	// make sure we are in range
	assert(entry < m_indirect_pens.size());
	assert(gfx.depth() <= 32);

	// either gfx->color_depth entries or as many as we can get up until the end
	int count = std::min(size_t(gfx.depth()), m_indirect_pens.size() - entry);

	// set a bit anywhere the transcolor matches
	u32 mask = 0;
	for (int bit = 0; bit < count; bit++)
		if (m_indirect_pens[entry++] == transcolor)
			mask |= 1 << bit;

	// return the final mask
	return mask;
}



//**************************************************************************
//  SHADOW TABLE CONFIGURATION
//**************************************************************************

//-------------------------------------------------
//  palette_set_shadow_mode(mode)
//
//      mode: 0 = use preset 0 (default shadow)
//            1 = use preset 1 (default highlight)
//            2 = use preset 2 *
//            3 = use preset 3 *
//
//  * Preset 2 & 3 work independently under 32bpp,
//    supporting up to four different types of
//    shadows at one time. They mirror preset 1 & 2
//    in lower depth settings to maintain
//    compatibility.
//
//
//  set_shadow_dRGB32(mode, dr, dg, db, noclip)
//
//      mode:    0 to   3 (which preset to configure)
//
//        dr: -255 to 255 ( red displacement )
//        dg: -255 to 255 ( green displacement )
//        db: -255 to 255 ( blue displacement )
//
//      noclip: 0 = resultant RGB clipped at 0x00/0xff
//              1 = resultant RGB wraparound 0x00/0xff
//
//
//  * Color shadows only work under 32bpp.
//    This function has no effect in lower color
//    depths where
//
//      set_shadow_factor() or
//      set_highlight_factor()
//
//    should be used instead.
//
//  * 32-bit shadows are lossy. Even with zero RGB
//    displacements the affected area will still look
//    slightly darkened.
//
//    Drivers should ensure all shadow pens in
//    gfx_drawmode_table[] are set to DRAWMODE_NONE
//    when RGB displacements are zero to avoid the
//    darkening effect.
//-------------------------------------------------

//-------------------------------------------------
//  set_shadow_dRGB32 - configure delta RGB values
//  for 1 of 4 shadow tables
//-------------------------------------------------

void device_palette_interface::set_shadow_dRGB32(int mode, int dr, int dg, int db, bool noclip)
{
	shadow_table_data &stable = m_shadow_tables[mode];

	// only applies to RGB direct modes
	assert(m_format != BITMAP_FORMAT_IND16);
	assert(stable.base != nullptr);

	// clamp the deltas (why?)
	if (dr < -0xff) dr = -0xff; else if (dr > 0xff) dr = 0xff;
	if (dg < -0xff) dg = -0xff; else if (dg > 0xff) dg = 0xff;
	if (db < -0xff) db = -0xff; else if (db > 0xff) db = 0xff;

	// early exit if nothing changed
	if (dr == stable.dr && dg == stable.dg && db == stable.db && noclip == stable.noclip)
		return;
	stable.dr = dr;
	stable.dg = dg;
	stable.db = db;
	stable.noclip = noclip;

	if (VERBOSE)
		device().popmessage("shadow %d recalc %d %d %d %02x", mode, dr, dg, db, noclip);

	// regenerate the table
	for (int i = 0; i < 32768; i++)
	{
		int r = pal5bit(i >> 10) + dr;
		int g = pal5bit(i >> 5) + dg;
		int b = pal5bit(i >> 0) + db;

		// apply clipping
		if (!noclip)
		{
			r = rgb_t::clamp(r);
			g = rgb_t::clamp(g);
			b = rgb_t::clamp(b);
		}
		rgb_t final = rgb_t(r, g, b);

		// store either 16 or 32 bit
		if (m_format == BITMAP_FORMAT_RGB32)
			stable.base[i] = final;
		else
			stable.base[i] = final.as_rgb15();
	}
}


//**************************************************************************
//  INTERNAL FUNCTIONS
//**************************************************************************

//-------------------------------------------------
//  allocate_palette - allocate and configure the
//  palette object itself
//-------------------------------------------------

void device_palette_interface::allocate_palette(u32 numentries)
{
	assert(numentries > 0);

	// determine the number of groups we need
	int numgroups = 1;
	if (palette_shadows_enabled())
		m_shadow_group = numgroups++;
	if (palette_hilights_enabled())
		m_hilight_group = numgroups++;
	if (numentries * numgroups > 65536)
		throw emu_fatalerror("%s(%s): Palette has more than 65536 colors.", device().shortname(), device().tag());

	// allocate a palette object containing all the colors and groups
	m_palette = palette_t::alloc(numentries, numgroups);

	// configure the groups
	if (m_shadow_group != 0)
		set_shadow_factor(PALETTE_DEFAULT_SHADOW_FACTOR);
	if (m_hilight_group != 0)
		set_highlight_factor(PALETTE_DEFAULT_HIGHLIGHT_FACTOR);

	// set the initial colors to a standard rainbow
	for (int index = 0; index < numentries; index++)
		set_pen_color(index, rgbexpand<1,1,1>(index, 0, 1, 2));

	// switch off the color mode
	switch (m_format)
	{
		// 16-bit paletteized case
		case BITMAP_FORMAT_IND16:
			m_black_pen = m_palette->black_entry();
			m_white_pen = m_palette->white_entry();
			if (m_black_pen >= 65536)
				m_black_pen = 0;
			if (m_white_pen >= 65536)
				m_white_pen = 65535;
			break;

		// 32-bit direct case
		case BITMAP_FORMAT_RGB32:
			m_black_pen = rgb_t::black();
			m_white_pen = rgb_t::white();
			break;

		// screenless case
		case BITMAP_FORMAT_INVALID:
		default:
			break;
	}
}


//-------------------------------------------------
//  allocate_color_tables - allocate memory for
//  pen and color tables
//-------------------------------------------------

void device_palette_interface::allocate_color_tables()
{
	int total_colors = m_palette->num_colors() * m_palette->num_groups();

	// allocate memory for the pen table
	switch (m_format)
	{
		case BITMAP_FORMAT_IND16:
			// create a dummy 1:1 mapping
			{
				m_pen_array.resize(total_colors + 2);
				pen_t *pentable = &m_pen_array[0];
				m_pens = &m_pen_array[0];
				for (int i = 0; i < total_colors + 2; i++)
					pentable[i] = i;
			}
			break;

		case BITMAP_FORMAT_RGB32:
			m_pens = reinterpret_cast<const pen_t *>(m_palette->entry_list_adjusted());
			break;

		default:
			m_pens = nullptr;
			break;
	}
}


//-------------------------------------------------
//  allocate_shadow_tables - allocate memory for
//  shadow tables
//-------------------------------------------------

void device_palette_interface::allocate_shadow_tables()
{
	int numentries = m_palette->num_colors();

	// if we have shadows, allocate shadow tables
	if (m_shadow_group != 0)
	{
		m_shadow_array.resize(65536);

		// palettized mode gets a single 64k table in slots 0 and 2
		if (m_format == BITMAP_FORMAT_IND16)
		{
			m_shadow_tables[0].base = m_shadow_tables[2].base = &m_shadow_array[0];
			for (int i = 0; i < 65536; i++)
				m_shadow_array[i] = (i < numentries) ? (i + numentries) : i;
		}

		// RGB mode gets two 32k tables in slots 0 and 2
		else
		{
			m_shadow_tables[0].base = &m_shadow_array[0];
			m_shadow_tables[2].base = &m_shadow_array[32768];
			configure_rgb_shadows(0, PALETTE_DEFAULT_SHADOW_FACTOR);
		}
	}

	// if we have hilights, allocate shadow tables
	if (m_hilight_group != 0)
	{
		m_hilight_array.resize(65536);

		// palettized mode gets a single 64k table in slots 1 and 3
		if (m_format == BITMAP_FORMAT_IND16)
		{
			m_shadow_tables[1].base = m_shadow_tables[3].base = &m_hilight_array[0];
			for (int i = 0; i < 65536; i++)
				m_hilight_array[i] = (i < numentries) ? (i + 2 * numentries) : i;
		}

		// RGB mode gets two 32k tables in slots 1 and 3
		else
		{
			m_shadow_tables[1].base = &m_hilight_array[0];
			m_shadow_tables[3].base = &m_hilight_array[32768];
			configure_rgb_shadows(1, PALETTE_DEFAULT_HIGHLIGHT_FACTOR);
		}
	}

	// set the default table
	m_shadow_table = m_shadow_tables[0].base;
}


//-------------------------------------------------
//  configure_rgb_shadows - configure shadows
//  for the RGB tables
//-------------------------------------------------

void device_palette_interface::configure_rgb_shadows(int mode, float factor)
{
	// only applies to RGB direct modes
	assert(m_format != BITMAP_FORMAT_IND16);

	// verify the shadow table
	assert(mode >= 0 && mode < std::size(m_shadow_tables));
	shadow_table_data &stable = m_shadow_tables[mode];
	assert(stable.base != nullptr);

	// regenerate the table
	int ifactor = int(factor * 256.0f);
	for (int rgb555 = 0; rgb555 < 32768; rgb555++)
	{
		u8 const r = rgb_t::clamp((pal5bit(rgb555 >> 10) * ifactor) >> 8);
		u8 const g = rgb_t::clamp((pal5bit(rgb555 >> 5) * ifactor) >> 8);
		u8 const b = rgb_t::clamp((pal5bit(rgb555 >> 0) * ifactor) >> 8);

		// store either 16 or 32 bit
		rgb_t final = rgb_t(r, g, b);
		if (m_format == BITMAP_FORMAT_RGB32)
			stable.base[rgb555] = final;
		else
			stable.base[rgb555] = final.as_rgb15();
	}
}
