/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * 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 Thread test utilities
 *//*--------------------------------------------------------------------*/

#include "tcuThreadUtil.hpp"

#include "deClock.h"
#include "deMemory.h"

using std::vector;
using de::SharedPtr;

namespace tcu
{
namespace ThreadUtil
{

Event::Event (void)
	: m_result		(RESULT_NOT_READY)
	, m_waiterCount	(0)
	, m_waiters		(0, 0)
{
}

Event::~Event (void)
{
}

void Event::setResult (Result result)
{
	m_lock.lock();
	DE_ASSERT(m_result == RESULT_NOT_READY);
	m_result = result;
	m_lock.unlock();

	for (int i = 0; i < m_waiterCount; i++)
		m_waiters.increment();
}

Event::Result Event::waitReady (void)
{
	m_lock.lock();

	if (m_result == RESULT_NOT_READY)
		m_waiterCount++;
	else
	{
		m_lock.unlock();
		return m_result;
	}

	m_lock.unlock();

	m_waiters.decrement();

	return m_result;
}

Object::Object (const char* type, SharedPtr<Event> e)
	: m_type	(type)
	, m_modify	(e)
{
}

Object::~Object	(void)
{
}

void Object::read (SharedPtr<Event> event, std::vector<SharedPtr<Event> >& deps)
{
	// Make call depend on last modifying call
	deps.push_back(m_modify);

	// Add read dependency
	m_reads.push_back(event);
}

void Object::modify (SharedPtr<Event> event, std::vector<SharedPtr<Event> >& deps)
{
	// Make call depend on all reads
	for (int readNdx = 0; readNdx < (int)m_reads.size(); readNdx++)
	{
		deps.push_back(m_reads[readNdx]);
	}
	deps.push_back(m_modify);

	// Update last modifying call
	m_modify = event;

	// Clear read dependencies of last "version" of this object
	m_reads.clear();
}

Operation::Operation (const char* name)
	: m_name	(name)
	, m_event	(new Event)
{
}

Operation::~Operation (void)
{
}

void Operation::execute (Thread& thread)
{
	bool success = true;

	// Wait for dependencies and check that they succeeded
	for (int depNdx = 0; depNdx < (int)m_deps.size(); depNdx++)
	{
		if (m_deps[depNdx]->waitReady() != Event::RESULT_OK)
			success = false;
	}

	// Try execute operation
	if (success)
	{
		try
		{
			exec(thread);
		}
		catch (...)
		{
			// Got exception event failed
			m_event->setResult(Event::RESULT_FAILED);
			throw;
		}

		m_event->setResult(Event::RESULT_OK);
	}
	else
		// Some dependencies failed
		m_event->setResult(Event::RESULT_FAILED);

	// Release resources
	m_deps.clear();
	m_event = SharedPtr<Event>();
}

const MessageBuilder::EndToken Message::End = MessageBuilder::EndToken();

void MessageBuilder::operator<< (const EndToken&)
{
	m_thread.pushMessage(m_stream.str());
}

Thread::Thread (deUint32 seed)
	: m_random	(seed)
	, m_status	(THREADSTATUS_NOT_STARTED)
{
}

Thread::~Thread (void)
{
	for (int operationNdx = 0; operationNdx < (int)m_operations.size(); operationNdx++)
		delete m_operations[operationNdx];

	m_operations.clear();
}

deUint8* Thread::getDummyData (size_t size)
{
	if (m_dummyData.size() < size)
	{
		m_dummyData.resize(size);
	}

	return &(m_dummyData[0]);
}

void Thread::addOperation (Operation* operation)
{
	m_operations.push_back(operation);
}

void Thread::run (void)
{
	m_status = THREADSTATUS_RUNNING;
	bool initOk = false;

	// Reserve at least two messages for each operation
	m_messages.reserve(m_operations.size()*2);
	try
	{
		init();
		initOk = true;
		for (int operationNdx = 0; operationNdx < (int)m_operations.size(); operationNdx++)
			m_operations[operationNdx]->execute(*this);

		deinit();
		m_status =  THREADSTATUS_READY;
	}
	catch (const tcu::NotSupportedError& e)
	{
		newMessage() << "tcu::NotSupportedError '" << e.what() << "'" << Message::End;
		deinit();
		m_status = (initOk ? THREADSTATUS_NOT_SUPPORTED : THREADSTATUS_INIT_FAILED);
	}
	catch (const tcu::Exception& e)
	{
		newMessage() << "tcu::Exception '" << e.what() << "'" << Message::End;
		deinit();
		m_status = (initOk ? THREADSTATUS_FAILED : THREADSTATUS_INIT_FAILED);
	}
	catch (const std::exception& error)
	{
		newMessage() << "std::exception '" << error.what() << "'" << Message::End;
		deinit();
		m_status = (initOk ? THREADSTATUS_FAILED : THREADSTATUS_INIT_FAILED);
	}
	catch (...)
	{
		newMessage() << "Unkown exception" << Message::End;
		deinit();
		m_status = (initOk ? THREADSTATUS_FAILED : THREADSTATUS_INIT_FAILED);
	}
}

void Thread::exec (void)
{
	start();
}

void Thread::pushMessage (const std::string& str)
{
	de::ScopedLock lock(m_messageLock);
	m_messages.push_back(Message(deGetMicroseconds(), str.c_str()));
}

int Thread::getMessageCount	 (void) const
{
	de::ScopedLock lock(m_messageLock);
	return (int)(m_messages.size());
}

Message Thread::getMessage (int index) const
{
	de::ScopedLock lock(m_messageLock);
	return m_messages[index];
}


DataBlock::DataBlock (SharedPtr<Event> event)
	: Object("DataBlock", event)
{
}

void DataBlock::setData (size_t size, const void* data)
{
	m_data = std::vector<deUint8>(size);
	deMemcpy(&(m_data[0]), data, size);
}

CompareData::CompareData (SharedPtr<DataBlock> a, SharedPtr<DataBlock> b)
	: Operation	("CompareData")
	, m_a		(a)
	, m_b		(b)
{
	readObject(SharedPtr<Object>(a));
	readObject(SharedPtr<Object>(b));
}

void CompareData::exec (Thread& thread)
{
	bool result = true;
	DE_ASSERT(m_a->getSize() == m_b->getSize());

	thread.newMessage() << "Begin -- CompareData" << Message::End;

	for (int byteNdx = 0; byteNdx < (int)m_a->getSize(); byteNdx++)
	{
		if (m_a->getData()[byteNdx] != m_b->getData()[byteNdx])
		{
			result = false;
			thread.newMessage() << "CompareData failed at offset :" << byteNdx << Message::End;
			break;
		}
	}

	if (result)
		thread.newMessage() << "CompareData passed" << Message::End;
	else
		TCU_FAIL("Data comparision failed");

	thread.newMessage() << "End -- CompareData" << Message::End;
}

} // ThreadUtil
} // tcu