/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * 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.
 *
 *//*!
 * \file
 * \brief Screen clearing test.
 *//*--------------------------------------------------------------------*/

#include "es2fColorClearTest.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "tcuVector.hpp"
#include "tcuRenderTarget.hpp"
#include "deRandom.hpp"
#include "deInt32.h"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include <vector>

using tcu::RGBA;
using tcu::Surface;
using tcu::TestLog;

using namespace std;

namespace deqp
{
namespace gles2
{
namespace Functional
{

class ColorClearCase : public TestCase
{
public:
	ColorClearCase(Context& context, const char* name, int numIters, int numClearsMin, int numClearsMax, bool testAlpha, bool testScissoring, bool testColorMasks, bool firstClearFull)
		: TestCase			(context, name, name)
		, m_numIters		(numIters)
		, m_numClearsMin	(numClearsMin)
		, m_numClearsMax	(numClearsMax)
		, m_testAlpha		(testAlpha)
		, m_testScissoring	(testScissoring)
		, m_testColorMasks	(testColorMasks)
		, m_firstClearFull	(firstClearFull)
	{
		m_curIter = 0;
	}

	virtual ~ColorClearCase (void)
	{
	}

	virtual IterateResult iterate (void);

private:
	const int		m_numIters;
	const int		m_numClearsMin;
	const int		m_numClearsMax;
	const bool		m_testAlpha;
	const bool		m_testScissoring;
	const bool		m_testColorMasks;
	const bool		m_firstClearFull;

	int				m_curIter;
};

class ClearInfo
{
public:
	ClearInfo (const tcu::IVec4& rect, deUint8 colorMask, tcu::RGBA color)
		: m_rect(rect), m_colorMask(colorMask), m_color(color)
	{
	}

	tcu::IVec4		m_rect;
	deUint8			m_colorMask;
	tcu::RGBA		m_color;
};

TestCase::IterateResult ColorClearCase::iterate (void)
{
	TestLog&					log						= m_testCtx.getLog();
	const glw::Functions&		gl						= m_context.getRenderContext().getFunctions();
	const tcu::RenderTarget&	renderTarget			= m_context.getRenderTarget();
	const tcu::PixelFormat&		pixelFormat				= renderTarget.getPixelFormat();
	const int					targetWidth				= renderTarget.getWidth();
	const int					targetHeight			= renderTarget.getHeight();
	const int					numPixels				= targetWidth * targetHeight;

	de::Random					rnd						(deInt32Hash(m_curIter));
	vector<deUint8>				pixelKnownChannelMask	(numPixels, 0);
	Surface						refImage				(targetWidth, targetHeight);
	Surface						resImage				(targetWidth, targetHeight);
	Surface						diffImage				(targetWidth, targetHeight);
	int							numClears				= rnd.getUint32() % (m_numClearsMax + 1 - m_numClearsMin) + m_numClearsMin;
	std::vector<ClearInfo>		clearOps;

	if (m_testScissoring)
		gl.enable(GL_SCISSOR_TEST);

	for (int clearNdx = 0; clearNdx < numClears; clearNdx++)
	{
		// Rectangle.
		int clearX;
		int clearY;
		int clearWidth;
		int clearHeight;
		if (!m_testScissoring || (clearNdx == 0 && m_firstClearFull))
		{
			clearX		= 0;
			clearY		= 0;
			clearWidth	= targetWidth;
			clearHeight	= targetHeight;
		}
		else
		{
			clearX		= (rnd.getUint32() % (2*targetWidth)) - targetWidth;
			clearY		= (rnd.getUint32() % (2*targetHeight)) - targetHeight;
			clearWidth	= (rnd.getUint32() % targetWidth);
			clearHeight	= (rnd.getUint32() % targetHeight);
		}
		gl.scissor(clearX, clearY, clearWidth, clearHeight);

		// Color.
		int		r = (int)(rnd.getUint32() & 0xFF);
		int		g = (int)(rnd.getUint32() & 0xFF);
		int		b = (int)(rnd.getUint32() & 0xFF);
		int		a = m_testAlpha ? (int)(rnd.getUint32() & 0xFF) : 0xFF;
		RGBA	clearCol(r, g, b, a);
		gl.clearColor(float(r)/255.0f, float(g)/255.0f, float(b)/255.0f, float(a)/255.0f);

		// Mask.
		deUint8	clearMask;
		if (!m_testColorMasks || (clearNdx == 0 && m_firstClearFull))
			clearMask = 0xF;
		else
			clearMask = (rnd.getUint32() & 0xF);
		gl.colorMask((clearMask&0x1) != 0, (clearMask&0x2) != 0, (clearMask&0x4) != 0, (clearMask&0x8) != 0);

		// Clear & store op.
		gl.clear(GL_COLOR_BUFFER_BIT);
		clearOps.push_back(ClearInfo(tcu::IVec4(clearX, clearY, clearWidth, clearHeight), clearMask, clearCol));

		// Let watchdog know we're alive.
		m_testCtx.touchWatchdog();
	}

	// Compute reference image.
	{
		for (int y = 0; y < targetHeight; y++)
		{
			std::vector<ClearInfo>	scanlineClearOps;

			// Find all rectangles affecting this scanline.
			for (int opNdx = 0; opNdx < (int)clearOps.size(); opNdx++)
			{
				ClearInfo& op = clearOps[opNdx];
				if (de::inBounds(y, op.m_rect.y(), op.m_rect.y()+op.m_rect.w()))
					scanlineClearOps.push_back(op);
			}

			// Compute reference for scanline.
			int x = 0;
			while (x < targetWidth)
			{
				tcu::RGBA	spanColor;
				deUint8		spanKnownMask	= 0;
				int			spanLength		= (targetWidth - x);

				for (int opNdx = (int)scanlineClearOps.size() - 1; opNdx >= 0; opNdx--)
				{
					const ClearInfo& op = scanlineClearOps[opNdx];

					if (de::inBounds(x, op.m_rect.x(), op.m_rect.x()+op.m_rect.z()) &&
						de::inBounds(y, op.m_rect.y(), op.m_rect.y()+op.m_rect.w()))
					{
						// Compute span length until end of given rectangle.
						spanLength = deMin32(spanLength, op.m_rect.x() + op.m_rect.z() - x);

						tcu::RGBA	clearCol	= op.m_color;
						deUint8		clearMask	= op.m_colorMask;
						if ((clearMask & 0x1) && !(spanKnownMask & 0x1)) spanColor.setRed(clearCol.getRed());
						if ((clearMask & 0x2) && !(spanKnownMask & 0x2)) spanColor.setGreen(clearCol.getGreen());
						if ((clearMask & 0x4) && !(spanKnownMask & 0x4)) spanColor.setBlue(clearCol.getBlue());
						if ((clearMask & 0x8) && !(spanKnownMask & 0x8)) spanColor.setAlpha(clearCol.getAlpha());
						spanKnownMask |= clearMask;

						// Break if have all colors.
						if (spanKnownMask == 0xF)
							break;
					}
					else if (op.m_rect.x() > x)
						spanLength = deMin32(spanLength, op.m_rect.x() - x);
				}

				// Set reference alpha channel to 0xFF, if no alpha in target.
				if (pixelFormat.alphaBits == 0)
					spanColor.setAlpha(0xFF);

				// Fill the span.
				for (int ndx = 0; ndx < spanLength; ndx++)
				{
					refImage.setPixel(x + ndx, y, spanColor);
					pixelKnownChannelMask[y*targetWidth + x + ndx] |= spanKnownMask;
				}

				x += spanLength;
			}
		}
	}

	glu::readPixels(m_context.getRenderContext(), 0, 0, resImage.getAccess());
	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels");

	// Compute difference image.
	RGBA colorThreshold = pixelFormat.getColorThreshold();
	RGBA matchColor(0, 255, 0, 255);
	RGBA diffColor(255, 0, 0, 255);
	RGBA maxDiff(0, 0, 0, 0);
	bool isImageOk = true;

	if (gl.isEnabled(GL_DITHER))
	{
		colorThreshold.setRed(colorThreshold.getRed() + 1);
		colorThreshold.setGreen(colorThreshold.getGreen() + 1);
		colorThreshold.setBlue(colorThreshold.getBlue() + 1);
		colorThreshold.setAlpha(colorThreshold.getAlpha() + 1);
	}

	for (int y = 0; y < targetHeight; y++)
	for (int x = 0; x < targetWidth; x++)
	{
		int			offset		= (y*targetWidth + x);
		RGBA		refRGBA		= refImage.getPixel(x, y);
		RGBA		resRGBA		= resImage.getPixel(x, y);
		deUint8		colMask		= pixelKnownChannelMask[offset];
		RGBA		diff		= computeAbsDiffMasked(refRGBA, resRGBA, colMask);
		bool		isPixelOk	= diff.isBelowThreshold(colorThreshold);

		diffImage.setPixel(x, y, isPixelOk ? matchColor : diffColor);

		isImageOk	= isImageOk && isPixelOk;
		maxDiff		= max(maxDiff, diff);
	}

	if (isImageOk)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	}
	else
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

		m_testCtx.getLog() << tcu::TestLog::Message << "Image comparison failed, max diff = " << maxDiff << ", threshold = " << colorThreshold << tcu::TestLog::EndMessage;

		log << TestLog::ImageSet("Result", "Resulting framebuffer")
			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
			<< TestLog::Image("Reference",	"Reference image",			refImage)
			<< TestLog::Image("DiffMask",	"Failing pixels",			diffImage)
			<< TestLog::EndImageSet;
		return TestCase::STOP;
	}

	bool isFinal = (++m_curIter == m_numIters);

	// On final frame, dump images.
	if (isFinal)
	{
		log << TestLog::ImageSet("Result", "Resulting framebuffer")
			<< TestLog::Image("Result",		"Resulting framebuffer",	resImage)
			<< TestLog::EndImageSet;
	}

	return isFinal ? TestCase::STOP : TestCase::CONTINUE;
}

ColorClearTest::ColorClearTest (Context& context) : TestCaseGroup(context, "color_clear", "Color Clear Tests")
{
}

ColorClearTest::~ColorClearTest (void)
{
}

void ColorClearTest::init (void)
{
	//										name					iters,	#..#,		alpha?,	scissor,masks,	1stfull?
	addChild(new ColorClearCase(m_context, "single_rgb",			30,		1,3,		false,	false,	false,	true	));
	addChild(new ColorClearCase(m_context, "single_rgba",			30,		1,3,		true,	false,	false,	true	));
	addChild(new ColorClearCase(m_context, "multiple_rgb",			15,		4,20,		false,	false,	false,	true	));
	addChild(new ColorClearCase(m_context, "multiple_rgba",			15,		4,20,		true,	false,	false,	true	));
	addChild(new ColorClearCase(m_context, "long_rgb",				2,		100,500,	false,	false,	false,	true	));
	addChild(new ColorClearCase(m_context, "long_rgba",				2,		100,500,	true,	false,	false,	true	));
	addChild(new ColorClearCase(m_context, "subclears_rgb",			15,		4,30,		false,	false,	false,	false	));
	addChild(new ColorClearCase(m_context, "subclears_rgba",		15,		4,30,		true,	false,	false,	false	));
	addChild(new ColorClearCase(m_context, "short_scissored_rgb",	30,		2,4,		false,	true,	false,	true	));
	addChild(new ColorClearCase(m_context, "scissored_rgb",			15,		4,30,		false,	true,	false,	true	));
	addChild(new ColorClearCase(m_context, "scissored_rgba",		15,		4,30,		true,	true,	false,	true	));
	addChild(new ColorClearCase(m_context, "masked_rgb",			15,		4,30,		false,	false,	true,	true	));
	addChild(new ColorClearCase(m_context, "masked_rgba",			15,		4,30,		true,	false,	true,	true	));
	addChild(new ColorClearCase(m_context, "masked_scissored_rgb",	15,		4,30,		false,	true,	true,	true	));
	addChild(new ColorClearCase(m_context, "masked_scissored_rgba",	15,		4,30,		true,	true,	true,	true	));
	addChild(new ColorClearCase(m_context, "complex_rgb",			15,		5,50,		false,	true,	true,	false	));
	addChild(new ColorClearCase(m_context, "complex_rgba",			15,		5,50,		true,	true,	true,	false	));
	addChild(new ColorClearCase(m_context, "long_masked_rgb",		2,		100,500,	false,	true,	true,	true	));
	addChild(new ColorClearCase(m_context, "long_masked_rgba",		2,		100,500,	true,	true,	true,	true	));
}

} // Functional
} // gles2
} // deqp
