/*****************************************************************************/
// Copyright 2007-2019 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:	Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

#include "dng_hue_sat_map.h"

#include "dng_assertions.h"
#include "dng_auto_ptr.h"
#include "dng_bottlenecks.h"
#include "dng_exceptions.h"
#include "dng_host.h"

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

std::atomic<uint64> dng_hue_sat_map::sRuntimeFingerprintCounter (0);

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

dng_hue_sat_map::dng_hue_sat_map ()

	:	fHueDivisions		(0)
	,	fSatDivisions		(0)
	,	fValDivisions		(0)
	,	fHueStep			(0)
	,	fValStep			(0)
	,	fRuntimeFingerprint ()
	,	fDeltas				()

	{
	
	}

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

dng_hue_sat_map::dng_hue_sat_map (const dng_hue_sat_map &src)

	:	fHueDivisions		(0)
	,	fSatDivisions		(0)
	,	fValDivisions		(0)
	,	fHueStep			(0)
	,	fValStep			(0)
	,	fRuntimeFingerprint ()
	,	fDeltas				()

	{
	
	*this = src;
	
	}

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

dng_hue_sat_map &dng_hue_sat_map::operator= (const dng_hue_sat_map &rhs)
	{ 

	if (this != &rhs)
		{

		if (!rhs.IsValid ())
			{

			SetInvalid ();

			}

		else
			{

			fHueDivisions = rhs.fHueDivisions;
			fSatDivisions = rhs.fSatDivisions;
			fValDivisions = rhs.fValDivisions;

			fHueStep = rhs.fHueStep;
			fValStep = rhs.fValStep;

			fRuntimeFingerprint = rhs.fRuntimeFingerprint;

			fDeltas = rhs.fDeltas;

			}

		}

	return *this;

	}

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

dng_hue_sat_map::~dng_hue_sat_map ()
	{
	
	}

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

void dng_hue_sat_map::SetDivisions (uint32 hueDivisions,
									uint32 satDivisions,
									uint32 valDivisions)
	{

	DNG_ASSERT (hueDivisions >= 1, "Must have at least 1 hue division.");
	DNG_ASSERT (satDivisions >= 2, "Must have at least 2 sat divisions.");
	
	if (valDivisions == 0)
		valDivisions = 1;

	if (hueDivisions == fHueDivisions &&
		satDivisions == fSatDivisions &&
		valDivisions == fValDivisions)
		{
		return;
		}

	fHueDivisions = hueDivisions;
	fSatDivisions = satDivisions;
	fValDivisions = valDivisions;
	
	fHueStep = satDivisions;
	fValStep = hueDivisions * fHueStep;

	dng_safe_uint32 size (DeltasCount ());

	size *= (uint32) sizeof (HSBModify);
	
	fDeltas.Allocate (size.Get ());
	
	DoZeroBytes (fDeltas.Buffer (), size.Get ());

	fRuntimeFingerprint.Clear ();

	}

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

void dng_hue_sat_map::GetDelta (uint32 hueDiv,
								uint32 satDiv,
								uint32 valDiv,
								HSBModify &modify) const
	{

	if (hueDiv >= fHueDivisions ||
		satDiv >= fSatDivisions ||
		valDiv >= fValDivisions ||
		fDeltas.Buffer () == NULL)
		{
		
		DNG_REPORT ("Bad parameters to dng_hue_sat_map::GetDelta");
		
		ThrowProgramError ();
		
		}

	int32 offset = valDiv * fValStep +
				   hueDiv * fHueStep +
				   satDiv;

	const HSBModify *deltas = GetConstDeltas ();

	modify.fHueShift = deltas [offset].fHueShift;
	modify.fSatScale = deltas [offset].fSatScale;
	modify.fValScale = deltas [offset].fValScale;

	}

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

void dng_hue_sat_map::SetDeltaKnownWriteable (uint32 hueDiv,
											  uint32 satDiv,
											  uint32 valDiv,
											  const HSBModify &modify)
	{

	if (hueDiv >= fHueDivisions ||
		satDiv >= fSatDivisions ||
		valDiv >= fValDivisions ||
		fDeltas.Buffer () == NULL)
		{
		
		DNG_REPORT ("Bad parameters to dng_hue_sat_map::SetDelta");
		
		ThrowProgramError ();
		
		}
		
	// Set this entry.
		
	int32 offset = valDiv * fValStep +
				   hueDiv * fHueStep +
				   satDiv;

	SafeGetDeltas () [offset] = modify;
	
	// The zero saturation entry is required to have a value scale
	// of 1.0f.
	
	if (satDiv == 0)
		{
		
		if (modify.fValScale != 1.0f)
			{
			
			#if qDNGValidate
		
			ReportWarning ("Value scale for zero saturation entries must be 1.0");
						 
			#endif
			
			SafeGetDeltas () [offset] . fValScale = 1.0f;
		
			}
		
		}
		
	// If we are settings the first saturation entry and we have not
	// set the zero saturation entry yet, fill in the zero saturation entry
	// by extrapolating first saturation entry.
	
	if (satDiv == 1)
		{
		
		HSBModify zeroSatModify;
		
		GetDelta (hueDiv, 0, valDiv, zeroSatModify);
		
		if (zeroSatModify.fValScale != 1.0f)
			{
			
			zeroSatModify.fHueShift = modify.fHueShift;
			zeroSatModify.fSatScale = modify.fSatScale;
			zeroSatModify.fValScale = 1.0f;
			
			SetDelta (hueDiv, 0, valDiv, zeroSatModify);
			
			}
		
		}

	}

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

void dng_hue_sat_map::AssignNewUniqueRuntimeFingerprint ()
	{

	const uint64 uid = ++sRuntimeFingerprintCounter;

	dng_md5_printer printer;
	printer.Process (&uid, sizeof (uid));
	fRuntimeFingerprint = printer.Result ();

	}

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

bool dng_hue_sat_map::operator== (const dng_hue_sat_map &rhs) const
	{
	
	if (fHueDivisions != rhs.fHueDivisions ||
		fSatDivisions != rhs.fSatDivisions ||
		fValDivisions != rhs.fValDivisions)
		return false;

	if (!IsValid ())
		return true;

	return memcmp (GetConstDeltas (),
				   rhs.GetConstDeltas (),
				   DeltasCount () * sizeof (HSBModify)) == 0;

	}

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

dng_hue_sat_map * dng_hue_sat_map::Interpolate (const dng_hue_sat_map &map1,
												const dng_hue_sat_map &map2,
												real64 weight1)
	{
	
	if (weight1 >= 1.0)
		{
		
		if (!map1.IsValid ())
			{
			
			DNG_REPORT ("map1 is not valid");
			
			ThrowProgramError ();
			
			}
			
		return new dng_hue_sat_map (map1);
		
		}
		
	if (weight1 <= 0.0)
		{
		
		if (!map2.IsValid ())
			{			
			DNG_REPORT ("map2 is not valid");
			
			ThrowProgramError ();
			
			}
			
		return new dng_hue_sat_map (map2);
		
		}
		
	// Both maps must be valid if we are using both.
	
	if (!map1.IsValid () || !map2.IsValid ())
		{
			
		DNG_REPORT ("map1 or map2 is not valid");
		
		ThrowProgramError ();
		
		}
		
	// Must have the same dimensions.
	
	if (map1.fHueDivisions != map2.fHueDivisions ||
		map1.fSatDivisions != map2.fSatDivisions ||
		map1.fValDivisions != map2.fValDivisions)
		{
		
		DNG_REPORT ("map1 and map2 have different sizes");
		
		ThrowProgramError ();
		
		}
		
	// Make table to hold interpolated results.
	
	AutoPtr<dng_hue_sat_map> result (new dng_hue_sat_map);
	
	result->SetDivisions (map1.fHueDivisions,
						  map1.fSatDivisions,
						  map1.fValDivisions);
						  
	// Interpolate between the tables.
	
	real32 w1 = (real32) weight1;
	real32 w2 = 1.0f - w1;
	
	const HSBModify *data1 = map1.GetConstDeltas ();
	const HSBModify *data2 = map2.GetConstDeltas ();
	
	HSBModify *data3 = result->SafeGetDeltas ();
	
	uint32 count = map1.DeltasCount ();
	
	for (uint32 index = 0; index < count; index++)
		{
		
		data3->fHueShift = w1 * data1->fHueShift +
						   w2 * data2->fHueShift;
						   
		data3->fSatScale = w1 * data1->fSatScale +
						   w2 * data2->fSatScale;
						   
		data3->fValScale = w1 * data1->fValScale +
						   w2 * data2->fValScale;
						   
		data1++;
		data2++;
		data3++;
		
		}

	// Compute a fingerprint based on the inputs for the new dng_hue_sat_map
	// so that repeated interpolations of the same objects with the same
	// parameters produce the same fingerprint each time.

		{

		dng_md5_printer printer;

		printer.Process ("Interpolate", 11);

		printer.Process (&weight1, sizeof(weight1));

		printer.Process (map1.RuntimeFingerprint ().data,
						 dng_fingerprint::kDNGFingerprintSize);

		printer.Process (map2.RuntimeFingerprint ().data,
						 dng_fingerprint::kDNGFingerprintSize);

		result->SetRuntimeFingerprint (printer.Result ());

		}

	// Return interpolated tables.
	
	return result.Release ();
		
	}

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

dng_hue_sat_map * dng_hue_sat_map::Interpolate (const dng_hue_sat_map &map1,
												const dng_hue_sat_map &map2,
												const dng_hue_sat_map &map3,
												const real64 weight1,
												const real64 weight2)
	{
	
	if (weight1 >= 1.0)
		{

		DNG_REQUIRE (map1.IsValid (), "map1 is not valid");
			
		return new dng_hue_sat_map (map1);
		
		}
		
	if (weight2 >= 1.0)
		{

		DNG_REQUIRE (map2.IsValid (), "map2 is not valid");
			
		return new dng_hue_sat_map (map2);
		
		}

	const real64 weight3 = 1.0 - (weight1 + weight2);
		
	if (weight3 >= 1.0)
		{

		DNG_REQUIRE (map3.IsValid (), "map3 is not valid");
			
		return new dng_hue_sat_map (map3);
		
		}

	// None of the weights can be negative.

	DNG_REQUIRE (weight1 >= 0.0, "Invalid weight1");
	DNG_REQUIRE (weight2 >= 0.0, "Invalid weight2");
	DNG_REQUIRE (weight3 >= 0.0, "Invalid weight3");

	// Must all be valid.

	DNG_REQUIRE (map1.IsValid (), "map1 is not valid");
	DNG_REQUIRE (map2.IsValid (), "map2 is not valid");
	DNG_REQUIRE (map3.IsValid (), "map3 is not valid");
				 
	// Must have the same dimensions.
	
	DNG_REQUIRE (map1.fHueDivisions == map2.fHueDivisions &&
				 map1.fHueDivisions == map3.fHueDivisions &&
				 map1.fSatDivisions == map2.fSatDivisions &&
				 map1.fSatDivisions == map3.fSatDivisions &&
				 map1.fValDivisions == map2.fValDivisions &&
				 map1.fValDivisions == map3.fValDivisions,
				 "map1, map2, map3 have different sizes");
		
	// Make table to hold interpolated results.
	
	AutoPtr<dng_hue_sat_map> result (new dng_hue_sat_map);
	
	result->SetDivisions (map1.fHueDivisions,
						  map1.fSatDivisions,
						  map1.fValDivisions);
						  
	// Interpolate between the tables.
	
	const real32 w1 = (real32) weight1;
	const real32 w2 = (real32) weight2;
	const real32 w3 = 1.0f - (w1 + w2);
	
	const HSBModify *data1 = map1.GetConstDeltas ();
	const HSBModify *data2 = map2.GetConstDeltas ();
	const HSBModify *data3 = map3.GetConstDeltas ();
	
	HSBModify *dataResult = result->SafeGetDeltas ();
	
	const uint32 count = map1.DeltasCount ();
	
	for (uint32 index = 0; index < count; index++)
		{
		
		dataResult->fHueShift = (w1 * data1->fHueShift +
								 w2 * data2->fHueShift +
								 w3 * data3->fHueShift);
						   
		dataResult->fSatScale = (w1 * data1->fSatScale +
								 w2 * data2->fSatScale +
								 w3 * data3->fSatScale);
						   
		dataResult->fValScale = (w1 * data1->fValScale +
								 w2 * data2->fValScale +
								 w3 * data3->fValScale);
						   
		data1++;
		data2++;
		data3++;

		dataResult++;
		
		}

	// Compute a fingerprint based on the inputs for the new dng_hue_sat_map
	// so that repeated interpolations of the same objects with the same
	// parameters produce the same fingerprint each time.

		{

		dng_md5_printer printer;

		printer.Process ("Interpolate3", 12);

		printer.Process (&weight1, sizeof (weight1));
		printer.Process (&weight2, sizeof (weight2));

		printer.Process (map1.RuntimeFingerprint ().data,
						 dng_fingerprint::kDNGFingerprintSize);

		printer.Process (map2.RuntimeFingerprint ().data,
						 dng_fingerprint::kDNGFingerprintSize);

		printer.Process (map3.RuntimeFingerprint ().data,
						 dng_fingerprint::kDNGFingerprintSize);

		result->SetRuntimeFingerprint (printer.Result ());

		}

	// Return interpolated table.
	
	return result.Release ();
		
	}

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