/*
 * Copyright 2015 The Etc2Comp Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
EtcBlock4x4Encoding.cpp

Block4x4Encoding is the abstract base class for the different encoders.  Each encoder targets a 
particular file format (e.g. ETC1, RGB8, RGBA8, R11)

*/

#include "EtcConfig.h"
#include "EtcBlock4x4Encoding.h"

#include "EtcBlock4x4EncodingBits.h"
#include "EtcBlock4x4.h"

#include <stdio.h>
#include <string.h>
#include <assert.h>

namespace Etc
{
	// ----------------------------------------------------------------------------------------------------
	//
	const float Block4x4Encoding::LUMA_WEIGHT = 3.0f;
	const float Block4x4Encoding::CHROMA_BLUE_WEIGHT = 0.5f;

	// ----------------------------------------------------------------------------------------------------
	//
	Block4x4Encoding::Block4x4Encoding(void)
	{

		m_pblockParent = nullptr;

		m_pafrgbaSource = nullptr;

		m_boolBorderPixels = false;

		m_fError = -1.0f;

		m_mode = MODE_UNKNOWN;

		m_uiEncodingIterations = 0;
		m_boolDone = false;

		for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++)
		{
			m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(-1.0f, -1.0f, -1.0f, -1.0f);
			m_afDecodedAlphas[uiPixel] = -1.0f;
		}

	}

	// ----------------------------------------------------------------------------------------------------
	// initialize the generic encoding for a 4x4 block
	// a_pblockParent points to the block associated with this encoding
	// a_errormetric is used to choose the best encoding
	// init the decoded pixels to -1 to mark them as undefined
	// init the error to -1 to mark it as undefined
	//
	void Block4x4Encoding::Init(Block4x4 *a_pblockParent,
								ColorFloatRGBA *a_pafrgbaSource,
								ErrorMetric a_errormetric)
	{

		m_pblockParent = a_pblockParent;

		m_pafrgbaSource = a_pafrgbaSource;

		m_boolBorderPixels = m_pblockParent->HasBorderPixels();

		m_fError = -1.0f;

		m_uiEncodingIterations = 0;

		m_errormetric = a_errormetric;

		for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++)
		{
			m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(-1.0f, -1.0f, -1.0f, -1.0f);
			m_afDecodedAlphas[uiPixel] = -1.0f;
		}

	}

	// ----------------------------------------------------------------------------------------------------
	// calculate the error for the block by summing the pixel errors
	//
	void Block4x4Encoding::CalcBlockError(void)
	{
		m_fError = 0.0f;

		for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++)
		{
			m_fError += CalcPixelError(m_afrgbaDecodedColors[uiPixel], m_afDecodedAlphas[uiPixel],
										m_pafrgbaSource[uiPixel]);
		}
		
	}

	// ----------------------------------------------------------------------------------------------------
	// calculate the error between the source pixel and the decoded pixel
	// the error amount is base on the error metric
	//
	float Block4x4Encoding::CalcPixelError(ColorFloatRGBA a_frgbaDecodedColor, float a_fDecodedAlpha,
											ColorFloatRGBA a_frgbaSourcePixel)
	{

		// if a border pixel
		if (isnan(a_frgbaSourcePixel.fA))
		{
			return 0.0f;
		}

		if (m_errormetric == ErrorMetric::RGBA)
		{
			assert(a_fDecodedAlpha >= 0.0f);

			float fDRed = (a_fDecodedAlpha * a_frgbaDecodedColor.fR) -
							(a_frgbaSourcePixel.fA * a_frgbaSourcePixel.fR);
			float fDGreen = (a_fDecodedAlpha * a_frgbaDecodedColor.fG) -
							(a_frgbaSourcePixel.fA * a_frgbaSourcePixel.fG);
			float fDBlue = (a_fDecodedAlpha * a_frgbaDecodedColor.fB) -
							(a_frgbaSourcePixel.fA * a_frgbaSourcePixel.fB);

			float fDAlpha = a_fDecodedAlpha - a_frgbaSourcePixel.fA;

			return fDRed*fDRed + fDGreen*fDGreen + fDBlue*fDBlue + fDAlpha*fDAlpha;
		}
		else if (m_errormetric == ErrorMetric::REC709)
		{
			assert(a_fDecodedAlpha >= 0.0f);

			float fLuma1 = a_frgbaSourcePixel.fR*0.2126f + a_frgbaSourcePixel.fG*0.7152f + a_frgbaSourcePixel.fB*0.0722f;
			float fChromaR1 = 0.5f * ((a_frgbaSourcePixel.fR - fLuma1) * (1.0f / (1.0f - 0.2126f)));
			float fChromaB1 = 0.5f * ((a_frgbaSourcePixel.fB - fLuma1) * (1.0f / (1.0f - 0.0722f)));

			float fLuma2 = a_frgbaDecodedColor.fR*0.2126f +
							a_frgbaDecodedColor.fG*0.7152f +
							a_frgbaDecodedColor.fB*0.0722f;
			float fChromaR2 = 0.5f * ((a_frgbaDecodedColor.fR - fLuma2) * (1.0f / (1.0f - 0.2126f)));
			float fChromaB2 = 0.5f * ((a_frgbaDecodedColor.fB - fLuma2) * (1.0f / (1.0f - 0.0722f)));

			float fDeltaL = a_frgbaSourcePixel.fA * fLuma1 - a_fDecodedAlpha * fLuma2;
			float fDeltaCr = a_frgbaSourcePixel.fA * fChromaR1 - a_fDecodedAlpha * fChromaR2;
			float fDeltaCb = a_frgbaSourcePixel.fA * fChromaB1 - a_fDecodedAlpha * fChromaB2;

			float fDAlpha = a_fDecodedAlpha - a_frgbaSourcePixel.fA;

			// Favor Luma accuracy over Chroma, and Red over Blue 
			return LUMA_WEIGHT*fDeltaL*fDeltaL +
					fDeltaCr*fDeltaCr +
					CHROMA_BLUE_WEIGHT*fDeltaCb*fDeltaCb +
					fDAlpha*fDAlpha;
	#if 0
			float fDRed = a_frgbaDecodedPixel.fR - a_frgbaSourcePixel.fR;
			float fDGreen = a_frgbaDecodedPixel.fG - a_frgbaSourcePixel.fG;
			float fDBlue = a_frgbaDecodedPixel.fB - a_frgbaSourcePixel.fB;
			return 2.0f * 3.0f * fDeltaL * fDeltaL + fDRed*fDRed + fDGreen*fDGreen + fDBlue*fDBlue;
#endif
		}
		else if (m_errormetric == ErrorMetric::NORMALXYZ)
		{
			float fDecodedX = 2.0f * a_frgbaDecodedColor.fR - 1.0f;
			float fDecodedY = 2.0f * a_frgbaDecodedColor.fG - 1.0f;
			float fDecodedZ = 2.0f * a_frgbaDecodedColor.fB - 1.0f;

			float fDecodedLength = sqrtf(fDecodedX*fDecodedX + fDecodedY*fDecodedY + fDecodedZ*fDecodedZ);

			if (fDecodedLength < 0.5f)
			{
				return 1.0f;
			}
			else if (fDecodedLength == 0.0f)
			{
				fDecodedX = 1.0f;
				fDecodedY = 0.0f;
				fDecodedZ = 0.0f;
			}
			else
			{
				fDecodedX /= fDecodedLength;
				fDecodedY /= fDecodedLength;
				fDecodedZ /= fDecodedLength;
			}

			float fSourceX = 2.0f * a_frgbaSourcePixel.fR - 1.0f;
			float fSourceY = 2.0f * a_frgbaSourcePixel.fG - 1.0f;
			float fSourceZ = 2.0f * a_frgbaSourcePixel.fB - 1.0f;

			float fSourceLength = sqrtf(fSourceX*fSourceX + fSourceY*fSourceY + fSourceZ*fSourceZ);

			if (fSourceLength == 0.0f)
			{
				fSourceX = 1.0f;
				fSourceY = 0.0f;
				fSourceZ = 0.0f;
			}
			else
			{
				fSourceX /= fSourceLength;
				fSourceY /= fSourceLength;
				fSourceZ /= fSourceLength;
			}

			float fDotProduct = fSourceX*fDecodedX + fSourceY*fDecodedY + fSourceZ*fDecodedZ;
			float fNormalizedDotProduct = 1.0f - 0.5f * (fDotProduct + 1.0f);
			float fDotProductError = fNormalizedDotProduct * fNormalizedDotProduct;
			
			float fLength2 = fDecodedX*fDecodedX + fDecodedY*fDecodedY + fDecodedZ*fDecodedZ;
			float fLength2Error = fabsf(1.0f - fLength2);

			float fDeltaW = a_frgbaDecodedColor.fA - a_frgbaSourcePixel.fA;
			float fErrorW = fDeltaW * fDeltaW;

			return fDotProductError + fLength2Error + fErrorW;
		}
		else // ErrorMetric::NUMERIC
		{
			assert(a_fDecodedAlpha >= 0.0f);

			float fDX = a_frgbaDecodedColor.fR - a_frgbaSourcePixel.fR;
			float fDY = a_frgbaDecodedColor.fG - a_frgbaSourcePixel.fG;
			float fDZ = a_frgbaDecodedColor.fB - a_frgbaSourcePixel.fB;
			float fDW = a_frgbaDecodedColor.fA - a_frgbaSourcePixel.fA;

			return fDX*fDX + fDY*fDY + fDZ*fDZ + fDW*fDW;
		}

	}

	// ----------------------------------------------------------------------------------------------------
	//

} // namespace Etc

