/*-------------------------------------------------------------------------
 * 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 RenderActivity base class.
 *//*--------------------------------------------------------------------*/

#include "tcuAndroidRenderActivity.hpp"
#include "deSemaphore.hpp"

#include <android/window.h>

#include <string>
#include <stdlib.h>

using std::string;

#if defined(DE_DEBUG)
#	define DBG_PRINT(X) print X
#else
#	define DBG_PRINT(X)
#endif

namespace tcu
{
namespace Android
{

enum
{
	MESSAGE_QUEUE_SIZE = 8 //!< Length of RenderThread message queue.
};

#if defined(DE_DEBUG)
static const char* getMessageTypeName (MessageType type)
{
	static const char* s_names[] =
	{
		"RESUME",
		"PAUSE",
		"FINISH",
		"WINDOW_CREATED",
		"WINDOW_RESIZED",
		"WINDOW_DESTROYED",
		"INPUT_QUEUE_CREATED",
		"INPUT_QUEUE_DESTROYED",
		"SYNC"
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == MESSAGETYPE_LAST);
	return s_names[type];
}
#endif

// RenderThread

RenderThread::RenderThread (NativeActivity& activity)
	: m_activity		(activity)
	, m_msgQueue		(MESSAGE_QUEUE_SIZE)
	, m_threadRunning	(false)
	, m_inputQueue		(DE_NULL)
	, m_windowState		(WINDOWSTATE_NOT_CREATED)
	, m_window			(DE_NULL)
	, m_paused			(false)
	, m_finish			(false)
	, m_receivedFirstResize(false)
{
}

RenderThread::~RenderThread (void)
{
}

void RenderThread::start (void)
{
	m_threadRunning = true;
	Thread::start();
}

void RenderThread::stop (void)
{
	// Queue finish command
	enqueue(Message(MESSAGE_FINISH));

	// Wait for thread to terminate
	join();

	m_threadRunning = false;
}

void RenderThread::enqueue (const Message& message)
{
	// \note Thread must be running or otherwise nobody is going to drain the queue.
	DE_ASSERT(m_threadRunning);
	m_msgQueue.pushFront(message);
}

void RenderThread::pause (void)
{
	enqueue(Message(MESSAGE_PAUSE));
}

void RenderThread::resume (void)
{
	enqueue(Message(MESSAGE_RESUME));
}

void RenderThread::sync (void)
{
	de::Semaphore waitSem(0);
	enqueue(Message(MESSAGE_SYNC, &waitSem));
	waitSem.decrement();
}

void RenderThread::processMessage (const Message& message)
{
	DBG_PRINT(("RenderThread::processMessage(): message = { %s, %p }\n", getMessageTypeName(message.type), message.payload.window));

	switch (message.type)
	{
		case MESSAGE_RESUME:	m_paused = false;	break;
		case MESSAGE_PAUSE:		m_paused = true;	break;
		case MESSAGE_FINISH:	m_finish = true;	break;

		// \note While Platform / WindowRegistry are currently multi-window -capable,
		//		 the fact that platform gives us windows too late / at unexpected times
		//		 forces us to do some sanity checking and limit system to one window here.
		case MESSAGE_WINDOW_CREATED:
			if (m_windowState != WINDOWSTATE_NOT_CREATED && m_windowState != WINDOWSTATE_DESTROYED)
				throw InternalError("Got unexpected onNativeWindowCreated() event from system");

			// The documented behavior for the callbacks is that the native activity
			// will get a call to onNativeWindowCreated(), at which point it should have
			// a surface to render to, and can then start immediately.
			//
			// The actual creation process has the framework making calls to both
			// onNativeWindowCreated() and then onNativeWindowResized(). The test
			// waits for that first resize before it considers the window ready for
			// rendering.
			//
			// However subsequent events in the framework may cause the window to be
			// recreated at a new position without a size change, which sends on
			// onNativeWindowDestroyed(), and then on onNativeWindowCreated() without
			// a follow-up onNativeWindowResized(). If this happens, the test will
			// stop rendering as it is no longer in the ready state, and a watchdog
			// thread will eventually kill the test, causing it to fail. We therefore
			// set the window state back to READY and process the window creation here
			// if we have already observed that first resize call.
			if (!m_receivedFirstResize) {
				m_windowState	= WINDOWSTATE_NOT_INITIALIZED;
			} else {
				m_windowState	= WINDOWSTATE_READY;
				onWindowCreated(message.payload.window);
			}
			m_window		= message.payload.window;
			break;

		case MESSAGE_WINDOW_RESIZED:
			if (m_window != message.payload.window)
				throw InternalError("Got onNativeWindowResized() event targeting different window");

			// Record that we've the first resize event, in case the window is
			// recreated later without a resize.
			m_receivedFirstResize = true;

			if (m_windowState == WINDOWSTATE_NOT_INITIALIZED)
			{
				// Got first resize event, window is ready for use.
				m_windowState = WINDOWSTATE_READY;
				onWindowCreated(message.payload.window);
			}
			else if (m_windowState == WINDOWSTATE_READY)
				onWindowResized(message.payload.window);
			else
				throw InternalError("Got unexpected onNativeWindowResized() event from system");

			break;

		case MESSAGE_WINDOW_DESTROYED:
			if (m_window != message.payload.window)
				throw InternalError("Got onNativeWindowDestroyed() event targeting different window");

			if (m_windowState != WINDOWSTATE_NOT_INITIALIZED && m_windowState != WINDOWSTATE_READY)
				throw InternalError("Got unexpected onNativeWindowDestroyed() event from system");

			if (m_windowState == WINDOWSTATE_READY)
				onWindowDestroyed(message.payload.window);

			m_windowState	= WINDOWSTATE_DESTROYED;
			m_window		= DE_NULL;
			break;

		case MESSAGE_INPUT_QUEUE_CREATED:
			m_inputQueue = message.payload.inputQueue;
			break;

		case MESSAGE_INPUT_QUEUE_DESTROYED:
			m_inputQueue = message.payload.inputQueue;
			break;

		case MESSAGE_SYNC:
			message.payload.semaphore->increment();
			break;

		default:
			throw std::runtime_error("Unknown message type");
			break;
	}
}

void RenderThread::run (void)
{
	// Init state
	m_windowState	= WINDOWSTATE_NOT_CREATED;
	m_paused		= true;
	m_finish		= false;

	try
	{
		while (!m_finish)
		{
			if (m_paused || m_windowState != WINDOWSTATE_READY)
			{
				// Block until we are not paused and window is ready.
				Message msg = m_msgQueue.popBack();
				processMessage(msg);
				continue;
			}

			// Process available commands
			{
				Message msg;
				if (m_msgQueue.tryPopBack(msg))
				{
					processMessage(msg);
					continue;
				}
			}

			DE_ASSERT(m_windowState == WINDOWSTATE_READY);

			// Process input events.
			// \todo [2013-05-08 pyry] What if system fills up the input queue before we have window ready?
			while (m_inputQueue &&
				   AInputQueue_hasEvents(m_inputQueue) > 0)
			{
				AInputEvent* event;
				TCU_CHECK(AInputQueue_getEvent(m_inputQueue, &event) >= 0);
				onInputEvent(event);
				AInputQueue_finishEvent(m_inputQueue, event, 1);
			}

			// Everything set up - safe to render.
			if (!render())
			{
				DBG_PRINT(("RenderThread::run(): render\n"));
				break;
			}
		}
	}
	catch (const std::exception& e)
	{
		print("RenderThread: %s\n", e.what());
	}

	// Tell activity to finish.
	DBG_PRINT(("RenderThread::run(): done, waiting for FINISH\n"));
	m_activity.finish();

	// Thread must keep draining message queue until FINISH message is encountered.
	try
	{
		while (!m_finish)
		{
			Message msg = m_msgQueue.popBack();

			// Ignore all but SYNC and FINISH messages.
			if (msg.type == MESSAGE_SYNC || msg.type == MESSAGE_FINISH)
				processMessage(msg);
		}
	}
	catch (const std::exception& e)
	{
		die("RenderThread: %s\n", e.what());
	}

	DBG_PRINT(("RenderThread::run(): exiting...\n"));
}

// RenderActivity

RenderActivity::RenderActivity (ANativeActivity* activity)
	: NativeActivity(activity)
	, m_thread		(DE_NULL)
{
	DBG_PRINT(("RenderActivity::RenderActivity()"));
}

RenderActivity::~RenderActivity (void)
{
	DBG_PRINT(("RenderActivity::~RenderActivity()"));
}

void RenderActivity::setThread (RenderThread* thread)
{
	m_thread = thread;
}

void RenderActivity::onStart (void)
{
	DBG_PRINT(("RenderActivity::onStart()"));
}

void RenderActivity::onResume (void)
{
	DBG_PRINT(("RenderActivity::onResume()"));

	// Resume (or start) test execution
	m_thread->resume();
}

void RenderActivity::onPause (void)
{
	DBG_PRINT(("RenderActivity::onPause()"));

	// Pause test execution
	m_thread->pause();
}

void RenderActivity::onStop (void)
{
	DBG_PRINT(("RenderActivity::onStop()"));
}

void RenderActivity::onDestroy (void)
{
	DBG_PRINT(("RenderActivity::onDestroy()"));
}

void RenderActivity::onNativeWindowCreated (ANativeWindow* window)
{
	DBG_PRINT(("RenderActivity::onNativeWindowCreated()"));
	m_thread->enqueue(Message(MESSAGE_WINDOW_CREATED, window));
}

void RenderActivity::onNativeWindowResized (ANativeWindow* window)
{
	DBG_PRINT(("RenderActivity::onNativeWindowResized()"));
	m_thread->enqueue(Message(MESSAGE_WINDOW_RESIZED, window));
}

void RenderActivity::onNativeWindowRedrawNeeded (ANativeWindow* window)
{
	DE_UNREF(window);
}

void RenderActivity::onNativeWindowDestroyed (ANativeWindow* window)
{
	DBG_PRINT(("RenderActivity::onNativeWindowDestroyed()"));
	m_thread->enqueue(Message(MESSAGE_WINDOW_DESTROYED, window));
	m_thread->sync(); // Block until thread has processed all messages.
}

void RenderActivity::onInputQueueCreated (AInputQueue* queue)
{
	DBG_PRINT(("RenderActivity::onInputQueueCreated()"));
	m_thread->enqueue(Message(MESSAGE_INPUT_QUEUE_CREATED, queue));
}

void RenderActivity::onInputQueueDestroyed (AInputQueue* queue)
{
	DBG_PRINT(("RenderActivity::onInputQueueDestroyed()"));
	m_thread->enqueue(Message(MESSAGE_INPUT_QUEUE_DESTROYED, queue));
	m_thread->sync();
}

} // Android
} // tcu