/*-------------------------------------------------------------------------
 * drawElements Quality Program Test Executor
 * ------------------------------------------
 *
 * 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 Extract shader programs from log.
 *//*--------------------------------------------------------------------*/

#include "xeTestLogParser.hpp"
#include "xeTestResultParser.hpp"
#include "deFilePath.hpp"
#include "deStringUtil.hpp"
#include "deString.h"

#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stdexcept>

using std::vector;
using std::string;
using std::set;
using std::map;

struct CommandLine
{
	CommandLine (void)
	{
	}

	string		filename;
	string		dstPath;
};

static const char* getShaderTypeSuffix (const xe::ri::Shader::ShaderType shaderType)
{
	switch (shaderType)
	{
		case xe::ri::Shader::SHADERTYPE_VERTEX:				return "vert";
		case xe::ri::Shader::SHADERTYPE_FRAGMENT:			return "frag";
		case xe::ri::Shader::SHADERTYPE_GEOMETRY:			return "geom";
		case xe::ri::Shader::SHADERTYPE_TESS_CONTROL:		return "tesc";
		case xe::ri::Shader::SHADERTYPE_TESS_EVALUATION:	return "tese";
		case xe::ri::Shader::SHADERTYPE_COMPUTE:			return "comp";
		default:
			throw xe::Error("Invalid shader type");
	}
}

static void writeShaderProgram (const CommandLine& cmdLine, const std::string& casePath, const xe::ri::ShaderProgram& shaderProgram, int programNdx)
{
	const string basePath = string(de::FilePath::join(cmdLine.dstPath, casePath).getPath()) + "." + de::toString(programNdx);

	for (int shaderNdx = 0; shaderNdx < shaderProgram.shaders.getNumItems(); shaderNdx++)
	{
		const xe::ri::Shader&	shader		= dynamic_cast<const xe::ri::Shader&>(shaderProgram.shaders.getItem(shaderNdx));
		const string			shaderPath	= basePath + "." + getShaderTypeSuffix(shader.shaderType);

		if (de::FilePath(shaderPath).exists())
			throw xe::Error("File '" + shaderPath + "' exists already");

		{
			std::ofstream out(shaderPath.c_str(), std::ifstream::binary|std::ifstream::out);

			if (!out.good())
				throw xe::Error("Failed to open '" + shaderPath + "'");

			out.write(shader.source.source.c_str(), shader.source.source.size());
		}
	}
}

struct StackEntry
{
	const xe::ri::List*		list;
	int						curNdx;

	explicit StackEntry (const xe::ri::List* list_) : list(list_), curNdx(0) {}
};

static void extractShaderPrograms (const CommandLine& cmdLine, const std::string& casePath, const xe::TestCaseResult& result)
{
	vector<StackEntry>	itemListStack;
	int					programNdx		= 0;

	itemListStack.push_back(StackEntry(&result.resultItems));

	while (!itemListStack.empty())
	{
		StackEntry& curEntry = itemListStack.back();

		if (curEntry.curNdx < curEntry.list->getNumItems())
		{
			const xe::ri::Item&	curItem	= curEntry.list->getItem(curEntry.curNdx);
			curEntry.curNdx += 1;

			if (curItem.getType() == xe::ri::TYPE_SHADERPROGRAM)
			{
				writeShaderProgram(cmdLine, casePath, static_cast<const xe::ri::ShaderProgram&>(curItem), programNdx);
				programNdx += 1;
			}
			else if (curItem.getType() == xe::ri::TYPE_SECTION)
				itemListStack.push_back(StackEntry(&static_cast<const xe::ri::Section&>(curItem).items));
		}
		else
			itemListStack.pop_back();
	}

	if (programNdx == 0)
		std::cout << "WARNING: no shader programs found in '" << casePath << "'\n";
}

class ShaderProgramExtractHandler : public xe::TestLogHandler
{
public:
	ShaderProgramExtractHandler (const CommandLine& cmdLine)
		: m_cmdLine(cmdLine)
	{
	}

	void setSessionInfo (const xe::SessionInfo&)
	{
		// Ignored.
	}

	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
	{
		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
	}

	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
	{
		// Ignored.
	}

	void testCaseResultComplete (const xe::TestCaseResultPtr& caseData)
	{
		if (caseData->getDataSize() > 0)
		{
			xe::TestCaseResult					fullResult;
			xe::TestResultParser::ParseResult	parseResult;

			m_testResultParser.init(&fullResult);
			parseResult = m_testResultParser.parse(caseData->getData(), caseData->getDataSize());
			DE_UNREF(parseResult);

			extractShaderPrograms(m_cmdLine, caseData->getTestCasePath(), fullResult);
		}
	}

private:
	const CommandLine&		m_cmdLine;
	xe::TestResultParser	m_testResultParser;
};

static void extractShaderProgramsFromLogFile (const CommandLine& cmdLine)
{
	std::ifstream					in				(cmdLine.filename.c_str(), std::ifstream::binary|std::ifstream::in);
	ShaderProgramExtractHandler		resultHandler	(cmdLine);
	xe::TestLogParser				parser			(&resultHandler);
	deUint8							buf				[1024];
	int								numRead			= 0;

	if (!in.good())
		throw std::runtime_error(string("Failed to open '") + cmdLine.filename + "'");

	for (;;)
	{
		in.read((char*)&buf[0], DE_LENGTH_OF_ARRAY(buf));
		numRead = (int)in.gcount();

		if (numRead <= 0)
			break;

		parser.parse(&buf[0], numRead);
	}

	in.close();
}

static void printHelp (const char* binName)
{
	printf("%s: [filename] [dst path (optional)]\n", binName);
}

static bool parseCommandLine (CommandLine& cmdLine, int argc, const char* const* argv)
{
	for (int argNdx = 1; argNdx < argc; argNdx++)
	{
		const char* arg = argv[argNdx];

		if (!deStringBeginsWith(arg, "--"))
		{
			if (cmdLine.filename.empty())
				cmdLine.filename = arg;
			else if (cmdLine.dstPath.empty())
				cmdLine.dstPath = arg;
			else
				return false;
		}
		else
			return false;
	}

	if (cmdLine.filename.empty())
		return false;

	return true;
}

int main (int argc, const char* const* argv)
{
	try
	{
		CommandLine cmdLine;

		if (!parseCommandLine(cmdLine, argc, argv))
		{
			printHelp(argv[0]);
			return -1;
		}

		extractShaderProgramsFromLogFile(cmdLine);
	}
	catch (const std::exception& e)
	{
		printf("FATAL ERROR: %s\n", e.what());
		return -1;
	}

	return 0;
}