/* Copyright (c) 2013-2018 Jeffrey Pfau
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VideoProxy.h"

#include "CoreController.h"

#include <QThread>

using namespace QGBA;

VideoProxy::VideoProxy() {
	mVideoLoggerRendererCreate(&m_logger.d, false);
	m_logger.d.block = true;
	m_logger.d.waitOnFlush = true;

	m_logger.d.init = &cbind<&VideoProxy::init>;
	m_logger.d.reset = &cbind<&VideoProxy::reset>;
	m_logger.d.deinit = &cbind<&VideoProxy::deinit>;
	m_logger.d.lock = &cbind<&VideoProxy::lock>;
	m_logger.d.unlock = &cbind<&VideoProxy::unlock>;
	m_logger.d.wait = &cbind<&VideoProxy::wait>;
	m_logger.d.wake = &callback<void, int>::func<&VideoProxy::wake>;

	m_logger.d.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>;
	m_logger.d.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>;
	m_logger.d.postEvent = &callback<void, enum mVideoLoggerEvent>::func<&VideoProxy::postEvent>;

	connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData);
}

void VideoProxy::attach(CoreController* controller) {
	CoreController::Interrupter interrupter(controller);
	controller->thread()->core->videoLogger = &m_logger.d;
}

void VideoProxy::detach(CoreController* controller) {
	CoreController::Interrupter interrupter(controller);
	if (controller->thread()->core->videoLogger == &m_logger.d) {
		controller->thread()->core->videoLogger = nullptr;
	}
}

void VideoProxy::processData() {
	mVideoLoggerRendererRun(&m_logger.d, false);
	m_fromThreadCond.wakeAll();
}

void VideoProxy::init() {
	RingFIFOInit(&m_dirtyQueue, 0x80000);
}

void VideoProxy::reset() {
	m_mutex.lock();
	RingFIFOClear(&m_dirtyQueue);
	m_toThreadCond.wakeAll();
	m_mutex.unlock();
}

void VideoProxy::deinit() {
	RingFIFODeinit(&m_dirtyQueue);
}

bool VideoProxy::writeData(const void* data, size_t length) {
	while (!RingFIFOWrite(&m_dirtyQueue, data, length)) {
		if (QThread::currentThread() == thread()) {
			// We're on the main thread
			mVideoLoggerRendererRun(&m_logger.d, false);
		} else {
			emit dataAvailable();
			m_mutex.lock();
			m_toThreadCond.wakeAll();
			m_fromThreadCond.wait(&m_mutex);
			m_mutex.unlock();
		}
	}
	return true;
}

bool VideoProxy::readData(void* data, size_t length, bool block) {
	bool read = false;
	while (true) {
		read = RingFIFORead(&m_dirtyQueue, data, length);
		if (!block || read) {
			break;
		}
		m_mutex.lock();
		m_fromThreadCond.wakeAll();
		m_toThreadCond.wait(&m_mutex);
		m_mutex.unlock();
	}
	return read;
}

void VideoProxy::postEvent(enum mVideoLoggerEvent event) {
	if (QThread::currentThread() == thread()) {
		// We're on the main thread
		handleEvent(event);
	} else {
		QMetaObject::invokeMethod(this, "handleEvent", Qt::BlockingQueuedConnection, Q_ARG(int, event));
	}
}

void VideoProxy::handleEvent(int event) {
	m_mutex.lock();
	m_logger.d.handleEvent(&m_logger.d, static_cast<enum mVideoLoggerEvent>(event));
	m_mutex.unlock();
}

void VideoProxy::lock() {
	m_mutex.lock();
}

void VideoProxy::unlock() {
	m_mutex.unlock();
}

void VideoProxy::wait() {
	m_mutex.lock();
	while (RingFIFOSize(&m_dirtyQueue)) {
		if (QThread::currentThread() == thread()) {
			// We're on the main thread
			mVideoLoggerRendererRun(&m_logger.d, false);
		} else {
			emit dataAvailable();
			m_toThreadCond.wakeAll();
			m_fromThreadCond.wait(&m_mutex, 1);
		}
	}
	m_mutex.unlock();
}

void VideoProxy::wake(int y) {
	if ((y & 15) == 15) {
		emit dataAvailable();
		m_toThreadCond.wakeAll();
	}
}
