/*-------------------------------------------------------------------------
 * drawElements Quality Program Execution Server
 * ---------------------------------------------
 *
 * 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 Test Driver.
 *//*--------------------------------------------------------------------*/

#include "xsTestDriver.hpp"
#include "deClock.h"

#include <string>
#include <vector>
#include <cstdio>

using std::string;
using std::vector;

#if 0
#	define DBG_PRINT(X) printf X
#else
#	define DBG_PRINT(X)
#endif

namespace xs
{

TestDriver::TestDriver (xs::TestProcess* testProcess)
	: m_state				(STATE_NOT_STARTED)
	, m_lastExitCode		(0)
	, m_process				(testProcess)
	, m_lastProcessDataTime	(0)
	, m_dataMsgTmpBuf		(SEND_RECV_TMP_BUFFER_SIZE)
{
}

TestDriver::~TestDriver (void)
{
	reset();
}

void TestDriver::reset (void)
{
	m_process->cleanup();

	m_state = STATE_NOT_STARTED;
}

void TestDriver::startProcess (const char* name, const char* params, const char* workingDir, const char* caseList)
{
	try
	{
		m_process->start(name, params, workingDir, caseList);
		m_state = STATE_PROCESS_STARTED;
	}
	catch (const TestProcessException& e)
	{
		printf("Failed to launch test process: %s\n", e.what());
		m_state				= STATE_PROCESS_LAUNCH_FAILED;
		m_lastLaunchFailure	= e.what();
	}
}

void TestDriver::stopProcess (void)
{
	m_process->terminate();
}

bool TestDriver::poll (ByteBuffer& messageBuffer)
{
	switch (m_state)
	{
		case STATE_NOT_STARTED:
			return false; // Nothing to report.

		case STATE_PROCESS_LAUNCH_FAILED:
			DBG_PRINT(("  STATE_PROCESS_LAUNCH_FAILED\n"));
			if (writeMessage(messageBuffer, ProcessLaunchFailedMessage(m_lastLaunchFailure.c_str())))
			{
				m_state				= STATE_NOT_STARTED;
				m_lastLaunchFailure	= "";
				return true;
			}
			else
				return false;

		case STATE_PROCESS_STARTED:
			DBG_PRINT(("  STATE_PROCESS_STARTED\n"));
			if (writeMessage(messageBuffer, ProcessStartedMessage()))
			{
				m_state = STATE_PROCESS_RUNNING;
				return true;
			}
			else
				return false;

		case STATE_PROCESS_RUNNING:
		{
			DBG_PRINT(("  STATE_PROCESS_RUNNING\n"));
			bool gotProcessData = false;

			// Poll log file and info buffer.
			gotProcessData = pollLogFile(messageBuffer)	|| gotProcessData;
			gotProcessData = pollInfo(messageBuffer)	|| gotProcessData;

			if (gotProcessData)
				return true; // Got IO.

			if (!m_process->isRunning())
			{
				// Process died.
				m_state					= STATE_READING_DATA;
				m_lastExitCode			= m_process->getExitCode();
				m_lastProcessDataTime	= deGetMicroseconds();

				return true; // Got state change.
			}

			return false; // Nothing to report.
		}

		case STATE_READING_DATA:
		{
			DBG_PRINT(("  STATE_READING_DATA\n"));
			bool gotProcessData = false;

			// Poll log file and info buffer.
			gotProcessData = pollLogFile(messageBuffer)	|| gotProcessData;
			gotProcessData = pollInfo(messageBuffer)	|| gotProcessData;

			if (gotProcessData)
			{
				// Got data.
				m_lastProcessDataTime = deGetMicroseconds();
				return true;
			}
			else if (deGetMicroseconds() - m_lastProcessDataTime > READ_DATA_TIMEOUT*1000)
			{
				// Read timeout occurred.
				m_state = STATE_PROCESS_FINISHED;
				return true; // State change.
			}
			else
				return false; // Still waiting for data.
		}

		case STATE_PROCESS_FINISHED:
			DBG_PRINT(("  STATE_PROCESS_FINISHED\n"));
			if (writeMessage(messageBuffer, ProcessFinishedMessage(m_lastExitCode)))
			{
				// Signal TestProcess to clean up any remaining resources.
				m_process->cleanup();

				m_state			= STATE_NOT_STARTED;
				m_lastExitCode	= 0;
				return true;
			}
			else
				return false;

		default:
			DE_ASSERT(DE_FALSE);
			return false;
	}
}

bool TestDriver::pollLogFile (ByteBuffer& messageBuffer)
{
	return pollBuffer(messageBuffer, MESSAGETYPE_PROCESS_LOG_DATA);
}

bool TestDriver::pollInfo (ByteBuffer& messageBuffer)
{
	return pollBuffer(messageBuffer, MESSAGETYPE_INFO);
}

bool TestDriver::pollBuffer (ByteBuffer& messageBuffer, MessageType msgType)
{
	const int minBytesAvailable = MESSAGE_HEADER_SIZE + MIN_MSG_PAYLOAD_SIZE;

	if (messageBuffer.getNumFree() < minBytesAvailable)
		return false; // Not enough space in message buffer.

	const int	maxMsgSize	= de::min((int)m_dataMsgTmpBuf.size(), messageBuffer.getNumFree());
	int			numRead		= 0;
	int			msgSize		= MESSAGE_HEADER_SIZE+1; // One byte is reserved for terminating 0.

	// Fill in data \note Last byte is reserved for 0.
	numRead = msgType == MESSAGETYPE_PROCESS_LOG_DATA
			? m_process->readTestLog(&m_dataMsgTmpBuf[MESSAGE_HEADER_SIZE], maxMsgSize-MESSAGE_HEADER_SIZE-1)
			: m_process->readInfoLog(&m_dataMsgTmpBuf[MESSAGE_HEADER_SIZE], maxMsgSize-MESSAGE_HEADER_SIZE-1);

	if (numRead <= 0)
		return false; // Didn't get any data.

	msgSize += numRead;

	// Terminate with 0.
	m_dataMsgTmpBuf[msgSize-1] = 0;

	// Write header.
	Message::writeHeader(msgType, msgSize, &m_dataMsgTmpBuf[0], MESSAGE_HEADER_SIZE);

	// Write to messagebuffer.
	messageBuffer.pushFront(&m_dataMsgTmpBuf[0], msgSize);

	DBG_PRINT(("  wrote %d bytes of %s data\n", msgSize, msgType == MESSAGETYPE_INFO ? "info" : "log"));

	return true;
}

bool TestDriver::writeMessage (ByteBuffer& messageBuffer, const Message& message)
{
	vector<deUint8> buf;
	message.write(buf);

	if (messageBuffer.getNumFree() < (int)buf.size())
		return false;

	messageBuffer.pushFront(&buf[0], (int)buf.size());
	return true;
}

} // xs