//================================================================================================
// Plugin for EQUINOX-3D
//
// Example to show how to create custom Node.
// This plugin visualizes the CIE  RGB <-> color spectrum curves.
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Feb-15 01:32:56
//
// Copyright (C) 2014-2026 By Gabor Nagy. All rights reserved.
//================================================================================================
#include <assert.h>
#include <stdio.h>

#include <ELists.h>
#include <EMalloc.h>
#include <EPlugins.h>

#include <E3D/Matrix.h>
#include <E3D/Scene.h>

#include <Xe/PushButton.h>
#include <Xe/Scale.h>

#include <Color/Panel.h>
#include <Color/Spectrum.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>


char*             E_PluginInfo = "Visualizes the CIE  RGB <-> color spectrum curves";

static EPlugin*   _Plugin = NULL;

static XeDWidget   _ToolBarButtons[16];static unsigned int	_NumOfToolBarButtons = 0;


//static const float  riXCurve[] = { 0.00013, 0.00023, 0.00041, 0.00074, 0.00137, 0.00223, 0.00424, 0.00765, 0.01431, 0.02319, 0.04351, 0.07763, 0.13438, 0.21477, 0.2839, 0.3285, 0.34828, 0.34806, 0.3362, 0.3187, 0.2908, 0.2511, 0.19536, 0.1421, 0.09564, 0.05795, 0.03201, 0.0147, 0.0049, 0.0024, 0.0093, 0.0291, 0.06327, 0.1096, 0.1655, 0.22575, 0.2904, 0.3597, 0.43345, 0.51205, 0.5945, 0.6784, 0.7621, 0.8425, 0.9163, 0.9786, 1.0263, 1.0567, 1.0622, 1.0456, 1.0026, 0.9384, 0.85445, 0.7514, 0.6424, 0.5419, 0.4479, 0.3608, 0.2835, 0.2187, 0.1649, 0.1212, 0.0874, 0.0636, 0.04677, 0.0329, 0.0227, 0.01584, 0.01136, 0.00811, 0.00579, 0.00411, 0.00289, 0.00205, 0.00144, 0.001, 0.00069, 0.00048, 0.00033, 0.00023, 0.00017, 0.00012, 8e-05, 6e-05, 4.1e-05, 2.9e-05, 2e-05, 1.4e-05, 1e-05 };
//static const float  riYCurve[] = { 0, 0, 1e-05, 2e-05, 4e-05, 6e-05, 0.00012, 0.00022, 0.0004, 0.00064, 0.0012, 0.00218, 0.004, 0.0073, 0.0116, 0.01684, 0.023, 0.0298, 0.038, 0.048, 0.06, 0.0739, 0.09098, 0.1126, 0.13902, 0.1693, 0.20802, 0.2586, 0.323, 0.4073, 0.503, 0.6082, 0.71, 0.7932, 0.862, 0.91485, 0.954, 0.9803, 0.99495, 1, 0.995, 0.9786, 0.952, 0.9154, 0.87, 0.8163, 0.757, 0.6949, 0.631, 0.5668, 0.503, 0.4412, 0.381, 0.321, 0.265, 0.217, 0.175, 0.1382, 0.107, 0.0816, 0.061, 0.04458, 0.032, 0.0232, 0.017, 0.01192, 0.00821, 0.00573, 0.0041, 0.00293, 0.00209, 0.00105, 0.00105, 0.00074, 0.00052, 0.00036, 0.00025, 0.00017, 0.00012, 8e-05, 6e-05, 4e-05, 3e-05, 2e-05, 1.4e-05, 1e-05, 7e-06, 5e-06, 3e-06 };
//static const float  riZCurve[] = { 0.00061, 0.00108, 0.00195, 0.00349, 0.00645, 0.01055, 0.02005, 0.03621, 0.06785, 0.1102, 0.2074, 0.3713, 0.6456, 1.03905, 1.3856, 1.62296, 1.74706, 1.7826, 1.77211, 1.7441, 1.6692, 1.5281, 1.28764, 1.0419, 0.81295, 0.6162, 0.46518, 0.3533, 0.272, 0.2123, 0.1582, 0.1117, 0.07825, 0.05725, 0.04216, 0.02984, 0.0203, 0.0134, 0.00875, 0.00575, 0.0039, 0.00275, 0.0021, 0.0018, 0.00165, 0.0014, 0.0011, 0.001, 0.0008, 0.0006, 0.00034, 0.00024, 0.00019, 0.0001, 5e-05, 3e-05, 2e-05, 1e-05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

//static const int  _CIE_count = sizeof(riXCurve) / sizeof(riXCurve[0]);
//EcCIEEntry        _CIE[sizeof(riXCurve) / sizeof(riXCurve[0])];


typedef struct
{
	float*  ResponseR;
	float*  ResponseG;
	float*  ResponseB;
	int     MaxIndexR;
	int     MaxIndexG;
	int     MaxIndexB;
	int     NumOfSpectralSamples;
	float   NumOfSpectralSamplesInv;

	float   RGBtoXYZ[16], XYZtoRGB[16];
} E3dSpectralConverter;


typedef struct
{
	E3dNodeComponentCore;

	float          WavelengthMin, WavelengthMax;

	float          WhiteOffsetRemoval;

	EcRGBf32Color  FromRGBfColor;
	EcRGBf32Color  RestoredRGBfColor;
	float*         Spectrum;

	E3dSpectralConverter  Converter;
	EBool                 SplinesCreated;
} E3dComponent;


#define ROffset(field)  (void*)(EPtrOffset(E3dComponent, field))
static EResource  _Resources[]=
{
	{ .Name= "WavelengthMin",         .Type= EResFLOAT32,  .Address= ROffset(WavelengthMin) },
	{ .Name= "WavelengthMax",         .Type= EResFLOAT32,  .Address= ROffset(WavelengthMax) },
	{ .Name= "WhiteOffsetRemoval",    .Type= EResFLOAT32,  .Address= ROffset(WhiteOffsetRemoval) },
	{ .Name= "FromRGBColor",          .Type= EResFLOAT32,  .Address= ROffset(FromRGBfColor),      .Ext.Count= 3 },
	{ .Name= "RestoredRGBColor",      .Type= EResFLOAT32,  .Address= ROffset(RestoredRGBfColor),  .Ext.Count= 3 },
	{ .Name= "NumOfSpectralSamples",  .Type= EResINT32,    .Address= ROffset(Converter.NumOfSpectralSamples) },
};
#undef ROffset


typedef struct
{
	XeWidget       NumOfSpectralSamplesW;

	XeWidget       WavelengthMinW, WavelengthMaxW, DCOffsetRemovalW;

	XeWidget       FromColorSwatchW;
	EcColorPanel*  FromColorPanel;

	XeWidget       RestoredColorSwatchW;
	EcColorPanel*  RestoredColorPanel;
} E3dResourcesGUI;


static E3dNodeComponentClass*  _NodeComponentClass = NULL;



//
// Matlab functions
//
E3dCoordinate* linspace_abn(const E3dCoordinate LA, const E3dCoordinate LB, const int LSteps)
{
	if(LSteps < 1) return(NULL);
	E3dCoordinate*  LArray;ENewN(LSteps, LArray);

	if(LSteps < 2)
	{
		LArray[0] = LB;
		return(LArray);
	}

	E3dCoordinate		LV = LA;
	int  LI = 0;const int  LN = LSteps - 1;const E3dCoordinate  LStep = (LB - LA) / (E3dCoordinate)LN;for(;LI < LN;LI++, LV += LStep)
	{
		LArray[LI] = LV;
	}
	LArray[LI] = LB;

	return(LArray);
}


static void _RGBToSpectrum(const EcRGBf32Color PRGB, const float PDCOffsetRemoval, float* PSpectrum, const int PNSpectralSamples)
{
	const EcRGBf32Color*  LInvFuncEntry = NULL;

	switch(PNSpectralSamples)
	{
		case 16:
		case 32:
		case 64:
			switch(PNSpectralSamples)
			{
				case 16:  LInvFuncEntry = Ec_CIE_Inv_380to720_16;break;
				case 32:  LInvFuncEntry = Ec_CIE_Inv_380to720_32;break;
				case 64:  LInvFuncEntry = Ec_CIE_Inv_380to720_64;break;
			}

			{
				float  LR = PRGB.R, LG = PRGB.G, LB = PRGB.B;
				float  LDCOffset;

				if(LR <= LG && LR <= LB)
				{
					LDCOffset = LR;
				}
				else if(LG <= LR && LG <= LB)
				{
					LDCOffset = LG;
				}
				else LDCOffset = LB;

				LDCOffset *= PDCOffsetRemoval;
				LR -= LDCOffset;
				LG -= LDCOffset;
				LB -= LDCOffset;

				int  LI = 0;const int  LN = PNSpectralSamples;for(;LI < LN;LI++, LInvFuncEntry++)
				{
					PSpectrum[LI] = ((LInvFuncEntry->R * LR + LInvFuncEntry->G * LG + LInvFuncEntry->B * LB) + LDCOffset);
				}

/*
printf("%s() %s:%d ", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
{
	for(LI = 0;LI < LNumOfSpectralSamples;LI++) printf(" % 5.2f", LSpectrum[LI]);
}
printf("\n");fflush(stdout);
*/

			}
		break;

		default:
			printf("%s() %s:%d %sNO DATABASE FOR %d SPECTRAL SAMPLES%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, PNSpectralSamples, ECodeNormal);fflush(stdout);
		break;
	}
}


static EChangeFlags _Update(E3dNode* PChangedBranchRootNode, E3dNode* PNode, E3dNodeComponent* PInfo, const EChangeFlags PChanged, const EBool PCheckOnly)
{
	if(PCheckOnly) return(0);

	E3dComponent*  LComponent = (E3dComponent*)PInfo;
	E3dScene*      LScene = E3d_Scene;
	E3dModel*      LModel = (E3dModel*)PNode;
	E3dSpline*     LSplineR = NULL;E3dSpline*  LSplineG = NULL;E3dSpline*  LSplineB = NULL;
	E3dSpline*     LSplineR1 = NULL;E3dSpline*  LSplineG1 = NULL;E3dSpline*  LSplineB1 = NULL;

	E3dSpline*     LSplineSpectrum = NULL;
	EChangeFlags   LTriggeredChanges = 0;


	if(LComponent == NULL)
	{
		LComponent = (E3dComponent*)E3dNodeComponent_Add((E3dNode*)LModel, _NodeComponentClass);
	}

	if(!LComponent->SplinesCreated)
	{
		LSplineR = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineR, E3dScene_GetUniqueNodeName(LScene, "r"), FALSE);

		LSplineG = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineG, E3dScene_GetUniqueNodeName(LScene, "g"), FALSE);

		LSplineB = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineB, E3dScene_GetUniqueNodeName(LScene, "b"), FALSE);


		LSplineR1 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineR1, E3dScene_GetUniqueNodeName(LScene, "r1"), FALSE);

		LSplineG1 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineG1, E3dScene_GetUniqueNodeName(LScene, "g1"), FALSE);

		LSplineB1 = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineB1, E3dScene_GetUniqueNodeName(LScene, "b1"), FALSE);


		LSplineSpectrum = (E3dSpline*)E3dModel_ObjectAddNew(LModel, E3dSpline_class);
		EObject_SetName(LSplineSpectrum, E3dScene_GetUniqueNodeName(LScene, "spectrum"), FALSE);
		LComponent->SplinesCreated = TRUE;
	}

	if(PChanged == 0)
	{
// Just check for consistency
//
		return(0);
	}
	else
	{
		if(LModel->Objects.Count == 7)
		{
			LSplineR  = E_DynamicCast(LModel->Objects.Items[0], E3dSpline);
			LSplineG  = E_DynamicCast(LModel->Objects.Items[1], E3dSpline);
			LSplineB  = E_DynamicCast(LModel->Objects.Items[2], E3dSpline);

			LSplineR1 = E_DynamicCast(LModel->Objects.Items[3], E3dSpline);
			LSplineG1 = E_DynamicCast(LModel->Objects.Items[4], E3dSpline);
			LSplineB1 = E_DynamicCast(LModel->Objects.Items[5], E3dSpline);

			LSplineSpectrum = E_DynamicCast(LModel->Objects.Items[6], E3dSpline);
		}
	}

	if(PChanged & E3dChgATTRIBUTES && LSplineR && LSplineG && LSplineB && LSplineR1 && LSplineG1 && LSplineB1 && LSplineSpectrum)
	{
		LSplineR->RGBAfColor = EcRGBAf32Color_s(0.625, 0.000, 0.000, 1.0);
		LSplineG->RGBAfColor = EcRGBAf32Color_s(0.000, 0.625, 0.000, 1.0);
		LSplineB->RGBAfColor = EcRGBAf32Color_s(0.000, 0.000, 0.625, 1.0);

		LSplineR1->RGBAfColor = EcRGBAf32Color_s(0.625, 0.000, 0.000, 1.0);
		LSplineG1->RGBAfColor = EcRGBAf32Color_s(0.000, 0.625, 0.000, 1.0);
		LSplineB1->RGBAfColor = EcRGBAf32Color_s(0.000, 0.000, 0.625, 1.0);

		LSplineSpectrum->RGBAfColor = EcRGBAf32Color_s(0.875, 0.875, 0.000, 1.0);

		const E3dCoordinate	LWavelengthMin = LComponent->WavelengthMin;
		const E3dCoordinate	LWavelengthMax = LComponent->WavelengthMax;


// OFFLINE - convert the Ec_CIE table for Matlab
//
/*
{
	const EcCIEEntry*  LCIE;
	int                LI;

	printf("riXCurve = [ ");LCIE = Ec_CIE;
	for(LI = 0;LI < Ec_CIE_count;LI++, LCIE++) printf(" % 11.12f", LCIE->R);
	printf(" ];\n");fflush(stdout);

	printf("riYCurve = [ ");LCIE = Ec_CIE;
	for(LI = 0;LI < Ec_CIE_count;LI++, LCIE++) printf(" % 11.12f", LCIE->G);
	printf(" ];\n");fflush(stdout);

	printf("riZCurve = [ ");LCIE = Ec_CIE;
	for(LI = 0;LI < Ec_CIE_count;LI++, LCIE++) printf(" % 11.12f", LCIE->B);
	printf(" ];\n");fflush(stdout);
}
*/


// DEBUG - just for testing, use the CIE XYZ data from the Smits' Matlab example
//
/*
	{
		 float        LWavelength = 360.0;
		 EcCIEEntry*  LCIE = _CIE;
		 int	LI = 0;for(;LI < _CIE_count;LI++, LWavelength += 5.0, LCIE++)
		 {
			 LCIE->Wavelength = LWavelength;
			 LCIE->R = riXCurve[LI];
			 LCIE->G = riYCurve[LI];
			 LCIE->B = riZCurve[LI];
		 }
		}
		const EcCIEEntry*  LCIE = _CIE;const int  LTotalCIECount = _CIE_count;
*/

		const EcCIEEntry*  LCIE = Ec_CIE;const int  LTotalCIECount = Ec_CIE_count;


		const int	LNumOfSpectralSamples = LComponent->Converter.NumOfSpectralSamples;

		LTriggeredChanges |= E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;


		{
// CIE response curves down-sampled to LNumOfSpectralSamples
//
			float*  LResponseR;EResizeNT(float, LNumOfSpectralSamples,  LComponent->Converter.ResponseR);
			float*  LResponseG;EResizeNT(float, LNumOfSpectralSamples,  LComponent->Converter.ResponseG);
			float*  LResponseB;EResizeNT(float, LNumOfSpectralSamples,  LComponent->Converter.ResponseB);

			LResponseR = LComponent->Converter.ResponseR;
			LResponseG = LComponent->Converter.ResponseG;
			LResponseB = LComponent->Converter.ResponseB;

			Ec_SampleCIE(LCIE, LTotalCIECount,  LComponent->WavelengthMin, LComponent->WavelengthMax, LNumOfSpectralSamples, LResponseR, LResponseG, LResponseB,  LSplineR, LSplineG, LSplineB);

			{
				const int  LNumOfCVs = LNumOfSpectralSamples;

				if(LSplineR1->CVs.Count != LNumOfCVs) EResizeNCA(E3dSplineCV, LNumOfCVs,  LSplineR1->CVs.Items, LSplineR1->CVs.Count, LSplineR1->CVs.CountAllocated);
				if(LSplineG1->CVs.Count != LNumOfCVs) EResizeNCA(E3dSplineCV, LNumOfCVs,  LSplineG1->CVs.Items, LSplineG1->CVs.Count, LSplineG1->CVs.CountAllocated);
				if(LSplineB1->CVs.Count != LNumOfCVs) EResizeNCA(E3dSplineCV, LNumOfCVs,  LSplineB1->CVs.Items, LSplineB1->CVs.Count, LSplineB1->CVs.CountAllocated);

				LTriggeredChanges |= E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;

				E3dSplineCV*	LCVR = LSplineR1->CVs.Items;
				E3dSplineCV*	LCVG = LSplineG1->CVs.Items;
				E3dSplineCV*	LCVB = LSplineB1->CVs.Items;

				const EcCIEEntry*	LEntry = LCIE;

				const E3dCoordinate	LYOffset = -2.5;

				const E3dCoordinate	LSampleWeightInv = (E3dCoordinate)LNumOfSpectralSamples / 3.0;

				E3dCoordinate	LWavelength = LWavelengthMin;
				const E3dCoordinate	LWavelengthStep = (LWavelengthMax - LWavelengthMin) / (E3dCoordinate)(LNumOfSpectralSamples - 1);
				int  LI = 0;for(;LI < LNumOfSpectralSamples;LI++, LEntry++, LCVR++, LCVG++, LCVB++, LWavelength += LWavelengthStep)
				{
					E3dCoordinate  LF;

					LF = LResponseR[LI];
					E3dSplineCV_Init(LCVR, E3dSplineLINEAR);
					LCVR->Position.X = LWavelength;
					LCVR->Position.Y = LF * LSampleWeightInv + LYOffset;
					LCVR->Position.Z = 0.0;

					LF = LResponseG[LI];
					E3dSplineCV_Init(LCVG, E3dSplineLINEAR);
					LCVG->Position.X = LWavelength;
					LCVG->Position.Y = LF * LSampleWeightInv + LYOffset;
					LCVG->Position.Z = 0.0;

					LF = LResponseB[LI];
					E3dSplineCV_Init(LCVB, E3dSplineLINEAR);
					LCVB->Position.X = LWavelength;
					LCVB->Position.Y = LF * LSampleWeightInv + LYOffset;
					LCVB->Position.Z = 0.0;
				}
			}

// Convert RGB to spectrum and visualize it
//
			{
				if(LSplineSpectrum->CVs.Count != LNumOfSpectralSamples) EResizeNCA(E3dSplineCV, LNumOfSpectralSamples,  LSplineSpectrum->CVs.Items, LSplineSpectrum->CVs.Count, LSplineSpectrum->CVs.CountAllocated);

				EResizeNT(float, LNumOfSpectralSamples,  LComponent->Spectrum);

				E3dSpectralConverter*  LConverter = &LComponent->Converter;
				LConverter->ResponseR = LResponseR;
				LConverter->ResponseG = LResponseG;
				LConverter->ResponseB = LResponseB;

				Ec_RGBtoXYZInit(EcsRGB, LConverter->RGBtoXYZ);Ec_Matrix3x3Invert_f32(LConverter->RGBtoXYZ, LConverter->XYZtoRGB);


/*
function  A = Init(minLambda, maxLambda)
global    N;

XYZtoRGB = InitXYZtoRGB;

vals = linspace(minLambda,maxLambda,N+1);
sum = zeros(3,1);
for i = 1:N
   A(:,i) = zeros(3,1);
   for j = vals(i):(vals(i+1) -1)   %don't add the endpoints twice...
       sum = sum + GetXYZ(j);       %NOTE: this cause problems for non-integer
       A(:,i) = A(:,i) + GetRGB(j); %endpoints....
   end;
end;
sum
return;
*/

/*
    const E3dCoordinate  minLambda = 380.0, maxLambda = 720.0, N = LNumOfSpectralSamples;

    E3dCoordinate*  vals = linspace_abn(minLambda, maxLambda, N + 1);
    E3dCoordinate   sum[3] = { 0.0, 0.0, 0.0 };
    E3dCoordinate*  A;ENewN(N * 3, A);

    int  i = 0;for(;i < N;i++)
    {
			A[0 + i * 3] = 0.0;A[1 + i * 3] = 0.0;A[2 + i * 3] = 0.0;
			int  j = vals[i];for(;j < vals[i + 1] - 1;j++)
			{
				float  LX, LY, LZ;
				E3dSpline_GetYValueFromX(LSplineR, j, &LX);
				E3dSpline_GetYValueFromX(LSplineG, j, &LY);
				E3dSpline_GetYValueFromX(LSplineB, j, &LZ);
				sum[0] += LX;
				sum[1] += LY;
				sum[2] += LZ;


				float  LR, LG, LB;EMatrix_Transform3x3_vv(LConverter->XYZtoRGB,  LX, LY, LZ,  LR, LG, LB);
				A[0 + i * 3] += LR;
				A[1 + i * 3] += LG;
				A[2 + i * 3] += LB;
			}
    }

    EFree(vals);
    EFree(A);
*/

				LConverter->NumOfSpectralSamples = LNumOfSpectralSamples;
				LConverter->NumOfSpectralSamplesInv = 1.0 / (float)LNumOfSpectralSamples;

				_RGBToSpectrum(LComponent->FromRGBfColor, LComponent->WhiteOffsetRemoval, LComponent->Spectrum, LConverter->NumOfSpectralSamples);

				const float*         LSpectrum = LComponent->Spectrum;
				const E3dCoordinate  LYOffset = -5.0;
				E3dCoordinate        LWavelength = LWavelengthMin;
				E3dSplineCV*         LCV = LSplineSpectrum->CVs.Items;
				const E3dCoordinate  LWavelengthStep = (LWavelengthMax - LWavelengthMin) / (E3dCoordinate)(LNumOfSpectralSamples - 1);
				int  LI = 0;for(;LI < LNumOfSpectralSamples;LI++, LCV++, LWavelength += LWavelengthStep)
				{
					E3dSplineCV_Init(LCV, E3dSplineLINEAR);
					LCV->Position.X = LWavelength;
					LCV->Position.Y = LSpectrum[LI] + LYOffset;
					LCV->Position.Z = 0.0;
				}
			}
		}

		E3dNode_UpdateForDisplay(PNode, E3dUpdateSplineSHAPE);
	}

	return(LTriggeredChanges);
}


//================================================
// ToolBar icon callback handler
//================================================
static void _CB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dModel*  LModel = E3dModel_CustomNew(E3d_Scene, _NodeComponentClass, NULL, FALSE);if(LModel)
	{
		E3dScene*  LScene = E3d_Scene;
		E3dScene_NodeHrcAdd(LScene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);
	}

	if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) E3dNode_Edit((E3dNode*)LModel, _NodeComponentClass);

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY|E3dChgHIERARCHY);
}


//========================================
// Constructor
//========================================
static int _Init(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dComponent*  LComponent = (E3dComponent*)PInfo;

	LComponent->WavelengthMin = 380.0;LComponent->WavelengthMax = 720.0;

	LComponent->WhiteOffsetRemoval = 0.0;

	LComponent->FromRGBfColor = EcRGBf32Color_s(0.0, 0.0, 0.0);
	LComponent->RestoredRGBfColor = EcRGBf32Color_s(0.0, 0.0, 0.0);

	LComponent->Spectrum = NULL;LComponent->Converter.NumOfSpectralSamples = 32;
	LComponent->Converter.ResponseR = NULL;
	LComponent->Converter.ResponseG = NULL;
	LComponent->Converter.ResponseB = NULL;

	return(E_SUCCESS);
}


//========================================
// Constructor
//========================================
static int _Free(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dComponent*  LComponent = (E3dComponent*)PInfo;

	EFreeChk(LComponent->Spectrum);
	EFreeChk(LComponent->Converter.ResponseR);
	EFreeChk(LComponent->Converter.ResponseG);
	EFreeChk(LComponent->Converter.ResponseB);

	return(E_SUCCESS);
}


#define _M_Copy(mField) LDInfo->mField = LSInfo->mField

//========================================
// Copy-constructor
//========================================
static int _Copy(const E3dNode* PSrcNode, const E3dNodeComponent* PSrcComponent,  E3dNode* PDstNode, E3dNodeComponent* PDstComponent)
{
	E3dComponent*  LSInfo = (E3dComponent*)PSrcComponent;
	E3dComponent*  LDInfo = (E3dComponent*)PDstComponent;

	_M_Copy(FromRGBfColor);
	_M_Copy(RestoredRGBfColor);

	_M_Copy(Converter.NumOfSpectralSamples);

	LDInfo->Spectrum = NULL;
	LDInfo->Converter.ResponseR = NULL;
	LDInfo->Converter.ResponseG = NULL;
	LDInfo->Converter.ResponseB = NULL;

	return(E_SUCCESS);
}


//========================================
// Color panel callback
//========================================
static void _DCB_ColorFrom(EcColorPanel* PColorPanel, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*            LPanel = (E3dNodePanel*)PClientData;
	E3dNode*                 LNode = LPanel->Node;
	E3dComponent*            LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);
	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _NodeComponentClass);
	E3dResourcesGUI*         LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);
	EcRGBf32Color            LRGBfColor;

	LRGBfColor.R = PColorPanel->RGBAfColors[0].R;
	LRGBfColor.G = PColorPanel->RGBAfColors[0].G;
	LRGBfColor.B = PColorPanel->RGBAfColors[0].B;
	LComponent->FromRGBfColor = LRGBfColor;

	{ EcRGBAiColor  LRGBAiColor;Ec_RGBf32ToRGBAi(&LRGBfColor, &LRGBAiColor);Xe_SetValuesVa(LResourcesGUI->FromColorSwatchW,  XeNBackgroundColor, &LRGBAiColor,  NULL); }

	_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);

	{
		const int  LNumOfSpectralSamples = LComponent->Converter.NumOfSpectralSamples;

		EFloat32_3  L = EFloat32_3s(0.0, 0.0, 0.0);

		int  LI = 0;for(;LI < LNumOfSpectralSamples;LI++)
		{
			const float  LS = LComponent->Spectrum[LI];
			L.X += LComponent->Converter.ResponseR[LI] * LS;
			L.Y += LComponent->Converter.ResponseG[LI] * LS;
			L.Z += LComponent->Converter.ResponseB[LI] * LS;
		}

		float  LR, LG, LB;EMatrix_Transform3x3_vs(LComponent->Converter.XYZtoRGB,  L,  LR, LG, LB);

		LComponent->RestoredRGBfColor.R = LR;
		LComponent->RestoredRGBfColor.G = LG;
		LComponent->RestoredRGBfColor.B = LB;
	}

	{ EcRGBAiColor  LRGBAiColor;Ec_RGBf32ToRGBAi(&LComponent->RestoredRGBfColor, &LRGBAiColor);Xe_SetValuesVa(LResourcesGUI->RestoredColorSwatchW,  XeNBackgroundColor, &LRGBAiColor,  NULL); }
	EcColorPanel_SetRGBfColor(LResourcesGUI->RestoredColorPanel, 0, &LComponent->RestoredRGBfColor);

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
}


static void _DCB_WavelengthMin(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*           LPanel = (E3dNodePanel*)PClientData;
	E3dNode*                LNode = LPanel->Node;
	E3dNodeComponentClass*  LClass = NULL;Xe_GetValuesVa(PW,  XeNUserData0, &LClass,  NULL);
	const int               LMinSpan = 2;

	{
		E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, LClass, FALSE);

		XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, LClass);if(LDrawer)
		{
			E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

			LComponent->WavelengthMin = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

			if(LComponent->WavelengthMin > LComponent->WavelengthMax - LMinSpan)
			{
				LComponent->WavelengthMax = LComponent->WavelengthMin + LMinSpan;
				XeScale_SetValue(LResourcesGUI->WavelengthMaxW, LComponent->WavelengthMax, FALSE);
			}

			_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);
			E3dScene*  LScene = E3d_Scene;
			E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
		}
	}
}


static void _DCB_WavelengthMax(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*           LPanel = (E3dNodePanel*)PClientData;
	E3dNode*                LNode = LPanel->Node;
	E3dNodeComponentClass*  LClass = NULL;Xe_GetValuesVa(PW,  XeNUserData0, &LClass,  NULL);
	const int               LMinSpan = 2;

	{
		E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, LClass, FALSE);

		XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, LClass);if(LDrawer)
		{
			E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

			LComponent->WavelengthMax = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

			if(LComponent->WavelengthMin > LComponent->WavelengthMax - LMinSpan)
			{
				LComponent->WavelengthMin = LComponent->WavelengthMax - LMinSpan;
				XeScale_SetValue(LResourcesGUI->WavelengthMinW, LComponent->WavelengthMin, FALSE);
			}

			_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);
			E3dScene*  LScene = E3d_Scene;
			E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
		}
	}
}


static void _DCB_WhiteOffsetRemoval(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*           LPanel = (E3dNodePanel*)PClientData;
	E3dNode*                LNode = LPanel->Node;
	E3dNodeComponentClass*  LClass = NULL;Xe_GetValuesVa(PW,  XeNUserData0, &LClass,  NULL);

	{
		E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, LClass, FALSE);

		LComponent->WhiteOffsetRemoval = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

		_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);
		E3dScene*  LScene = E3d_Scene;
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
	}
}


//========================================
// Dialog box callback handler
//========================================
static void _DCB_NumOfSpectralSamples(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dComponent*  LComponent = (E3dComponent*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LComponent->Converter.NumOfSpectralSamples = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

	_Update(NULL, LNode, (E3dNodeComponent*)LComponent, E3dChgATTRIBUTES, FALSE);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
}


static void _GUI(E3dNode* LNode, E3dNodeComponent* PInfo, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dComponent*            LComponent = (E3dComponent*)PInfo;

	switch(PAction)
	{
		case E_UPDATE:
			if(LComponent)
			{
				if(LDrawer->ResourcesGUI)
				{
					E3dResourcesGUI*	LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

					XeScale_SetValue(LResourcesGUI->NumOfSpectralSamplesW, LComponent->Converter.NumOfSpectralSamples, FALSE);

					XeScale_SetValue(LResourcesGUI->WavelengthMinW, LComponent->WavelengthMin, FALSE);
					XeScale_SetValue(LResourcesGUI->WavelengthMaxW, LComponent->WavelengthMax, FALSE);
					XeScale_SetValue_f32(LResourcesGUI->DCOffsetRemovalW, LComponent->WhiteOffsetRemoval, FALSE);

					{ EcRGBAiColor  LRGBAiColor;Ec_RGBf32ToRGBAi(&LComponent->FromRGBfColor, &LRGBAiColor);Xe_SetValuesVa(LResourcesGUI->FromColorSwatchW,  XeNBackgroundColor, &LRGBAiColor,  NULL);  }
					EcColorPanel_SetRGBfColor(LResourcesGUI->FromColorPanel, 0, &LComponent->FromRGBfColor);

					{ EcRGBAiColor  LRGBAiColor;Ec_RGBf32ToRGBAi(&LComponent->RestoredRGBfColor, &LRGBAiColor);Xe_SetValuesVa(LResourcesGUI->RestoredColorSwatchW,  XeNBackgroundColor, &LRGBAiColor,  NULL); }
					EcColorPanel_SetRGBfColor(LResourcesGUI->RestoredColorPanel, 0, &LComponent->RestoredRGBfColor);
				}
				else
				{
					XeWidget          LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
					XeWidget          LIndentWs[7];int	LIndentN;        // For indenting the Scale Widgets
					XeWidget          LTraverseWs[16];int	LTraverseN;    // For TextField traversal
					EArg              Args[256];int  ArgCnt;
					E3dResourcesGUI*  LResourcesGUI;ENew(LResourcesGUI);

					LDrawer->ResourcesGUI = LResourcesGUI;

					LTraverseN = 0;
					LIndentN = 0;

					EXeStart;
					EXeSetArg(XeNScaleLengthInPixels, 120);
					EXeSetArg(XeNTextFieldDigits, 5);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->NumOfSpectralSamplesW = LW = XeScale_New_i32(LTopMatrixW, "Samples", Args, ArgCnt);
					XeScale_SetValueAndRange(LW,  LComponent->Converter.NumOfSpectralSamples,  3, 256,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_NumOfSpectralSamples, (EPointer)PPanel);


					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->WavelengthMinW = LW = XeScale_New_i32(LTopMatrixW, "Wavelength min", Args, ArgCnt);
					XeScale_SetValueAndRange(LW,  LComponent->WavelengthMin,  350, 830,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_WavelengthMin, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->WavelengthMaxW = LW = XeScale_New_i32(LTopMatrixW, "Wavelength max", Args, ArgCnt);
					XeScale_SetValueAndRange(LW,  LComponent->WavelengthMax,  350, 830,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_WavelengthMax, (EPointer)PPanel);


					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 2);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNUserData0, LComponent->Class);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->DCOffsetRemovalW = LW = XeScale_ManagedNew_f32(LTopMatrixW, "WhiteOffsetRemoval", Args, ArgCnt);
					XeScale_SetValueAndRange_f32(LW,  LComponent->WhiteOffsetRemoval,  0.0, 1.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_WhiteOffsetRemoval, (EPointer)PPanel);


					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);

// Set up TextField traversal
//
					XeTraversal_SetRing(LTraverseWs, LTraverseN);


// Create ColorSwatches and ColorPanels
//
					LIndentN = 0;

					{
						LResourcesGUI->FromColorSwatchW = EcColorSwatch_NewRGBf("Color to spectralize", LTopMatrixW, &LW, NULL, NULL, NULL);
						LIndentWs[LIndentN++] = LW;
						XeWidget  LMatrixW = Xe_Parent(LResourcesGUI->FromColorSwatchW);

						EXeStart;
						EXeSetArg(XeNMarginTop, 12);
						EXeSetArg(XeNMarginBottom, 4);
						Xe_SetValues(LMatrixW, Args, ArgCnt);
						EcRGBAiColor  LRGBAiColor;Ec_RGBf32ToRGBAi(&LComponent->FromRGBfColor, &LRGBAiColor);
						Xe_SetValuesVa(LResourcesGUI->FromColorSwatchW,  XeNBackgroundColor, &LRGBAiColor,  NULL);
					}


					LResourcesGUI->FromColorPanel = EcColorPanel_NewVa(LTopMatrixW, "FromColorPanel",
					 XeNShadowType, XeShadowIN, XeNShadowThickness, 1,
					 XeNColorChangedCallback, _DCB_ColorFrom,
					 XeNColorChangedCallbackClientData, (EPointer)PPanel,
					 XeNColorComponentType, EResFLOAT32,
					 XeNRGBAfColor, &LComponent->FromRGBfColor,
					 XeNCreateColorWheel, TRUE,
					 XeNIntensityOn, TRUE,
					 XeNAlphaOn, FALSE, NULL);
					 EcColorPanel_SetRGBfColor(LResourcesGUI->FromColorPanel, 0, &LComponent->FromRGBfColor);
					XeWidget_Manage(LResourcesGUI->FromColorPanel->TopMatrixW);

					{
						LResourcesGUI->RestoredColorSwatchW = EcColorSwatch_NewRGBf("Restored color", LTopMatrixW, &LW, NULL, NULL, NULL);
						LIndentWs[LIndentN++] = LW;

						XeWidget  LMatrixW = Xe_Parent(LResourcesGUI->RestoredColorSwatchW);
						EXeStart;
						EXeSetArg(XeNMarginTop, 12);
						EXeSetArg(XeNMarginBottom, 4);
						Xe_SetValues(LMatrixW, Args, ArgCnt);
						EcRGBAiColor  LRGBAiColor;Ec_RGBf32ToRGBAi(&LComponent->RestoredRGBfColor, &LRGBAiColor);
						Xe_SetValuesVa(LResourcesGUI->RestoredColorSwatchW,  XeNBackgroundColor, &LRGBAiColor,  NULL);
					}

					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);


					LResourcesGUI->RestoredColorPanel = EcColorPanel_NewVa(LTopMatrixW, "RestoredColorPanel",
					 XeNShadowType, XeShadowIN, XeNShadowThickness, 1,
					 XeNColorComponentType, EResFLOAT32,
					 XeNRGBAfColor, &LComponent->RestoredRGBfColor,
					 XeNCreateColorWheel, TRUE,
					 XeNIntensityOn, TRUE,
					 XeNAlphaOn, FALSE, NULL);
					XeWidget_Manage(LResourcesGUI->RestoredColorPanel->TopMatrixW);
				}
			}
		break;

		case E_FREE:
		break;
	}
}


#define _Normalize(mNumOfSpectralSamples) do\
{\
	EResizeNT(float, mNumOfSpectralSamples,  LResponseR);\
	EResizeNT(float, mNumOfSpectralSamples,  LResponseG);\
	EResizeNT(float, mNumOfSpectralSamples,  LResponseB);\
\
	Ec_SampleCIE(LSplineR, LSplineG, LSplineB,  Ec_CIE, Ec_CIE_count,  380.0, 720.0, mNumOfSpectralSamples, LResponseR, LResponseG, LResponseB);\
\
	{\
		{\
			const float  LNumOfSpectralSamplesInv = 1.0 / (float)mNumOfSpectralSamples;\
			float  lX = 0.0, lY = 0.0, lZ = 0.0;\
			{\
				int  lC = 0;for(;lC < mNumOfSpectralSamples;lC++)\
				{\
					const float  lS = 1.0;  /* Constant spectrum	*/\
					lX += LResponseR[lC] * lS;\
					lY += LResponseG[lC] * lS;\
					lZ += LResponseB[lC] * lS;\
				}\
			}\
\
			float  lR, lG, lB;EMatrix_Transform3x3_vv(LXYZtoRGB,  lX, lY, lZ,  lR, lG, lB);\
\
			const float  lRestoredR = lR * LNumOfSpectralSamplesInv;\
			const float  lRestoredG = lG * LNumOfSpectralSamplesInv;\
			const float  lRestoredB = lB * LNumOfSpectralSamplesInv;\
\
			const float  lNormalizerR = 1.0 / lRestoredR / 3.0;\
			const float  lNormalizerG = 1.0 / lRestoredG / 3.0;\
			const float  lNormalizerB = 1.0 / lRestoredB / 3.0;\
			printf("%s() %s:%d Normalizer %7f %7f %7f\n", __FUNCTION__, __FILE__, __LINE__,  lNormalizerR, lNormalizerG, lNormalizerB);fflush(stdout);\
\
\
			const EcRGBf32Color*  LInvFuncEntry = NULL;\
			switch(mNumOfSpectralSamples)\
			{\
				case 16:\
				case 32:\
				case 64:\
					switch(mNumOfSpectralSamples)\
					{\
						case 16:  LInvFuncEntry = Ec_CIE_Inv_380to720_16;break;\
						case 32:  LInvFuncEntry = Ec_CIE_Inv_380to720_32;break;\
						case 64:  LInvFuncEntry = Ec_CIE_Inv_380to720_64;break;\
					}\
				break;\
\
				default:\
				break;\
			}\
\
			int  lC = 0;for(;lC < mNumOfSpectralSamples;lC++, LInvFuncEntry++)\
			{\
				printf(" { % 12.8f, % 12.8f, % 12.8f },\n", LInvFuncEntry->R * lNormalizerR, LInvFuncEntry->G * lNormalizerG, LInvFuncEntry->B * lNormalizerB);fflush(stdout);\
			}\
		}\
	}\
} while(0)


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;

	{
		E3dNodeComponentClass  LClass =
		{
			.Name=        "CIE RGB",
			.StructSize=  sizeof(E3dComponent),
			.Resources=   EListS_s(_Resources),
			.Init=        _Init,
			.Free=        _Free,
			.Copy=        _Copy,
			.Update=      _Update,
			.GUI=         _GUI
		};
		_NodeComponentClass = E3dNodeComponent_ClassRegister(&LClass);
	}

	{
		E3dSpline*  LSplineR;E_New(E3dSpline, LSplineR);
		E3dSpline*  LSplineG;E_New(E3dSpline, LSplineG);
		E3dSpline*  LSplineB;E_New(E3dSpline, LSplineB);

		float*  LResponseR = NULL;float*  LResponseG = NULL;float*  LResponseB = NULL;

		float  LRGBtoXYZ[16], LXYZtoRGB[16];
		Ec_RGBtoXYZInit(EcsRGB, LRGBtoXYZ);Ec_Matrix3x3Invert_f32(LRGBtoXYZ, LXYZtoRGB);


/*
		_Normalize(16);
		_Normalize(32);
		_Normalize(64);
*/

		EFreeChkClr(LResponseR);
		EFreeChkClr(LResponseG);
		EFreeChkClr(LResponseB);
		E_DeleteChk(LSplineR);
		E_DeleteChk(LSplineG);
		E_DeleteChk(LSplineB);
	}

	if(_NodeComponentClass)
	{
// Use default plugin icon
//
		XePushButton_AddImg3(_ToolBarButtons[_NumOfToolBarButtons++], Plugin, "Tool->Create", "CIE RGB", '\0', NULL, NULL, FALSE, "This plugin visualizes the CIE  RGB <-> color spectrum curves.", _CB, (EPointer)0);
	}

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		int  LI = 0;for(LI = 0;LI < _NumOfToolBarButtons;LI++)
		{
			XeItemFree(_ToolBarButtons[LI]);
		}
		_NumOfToolBarButtons = 0;
	}

	if(_NodeComponentClass)
	{
		E3dNodeComponent_ClassDeactivate(_NodeComponentClass);_NodeComponentClass = NULL;
	}

	return(E_SUCCESS);
}
