/*
	This file is part of Flycast.

    Flycast is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    Flycast is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "maplelink.h"
#include "cfg/option.h"
#include "hw/maple/maple_if.h"

std::array<std::array<std::list<MapleLink::Ptr>, 2>, 4> MapleLink::Links;
std::mutex MapleLink::Mutex;

namespace
{
struct GameState
{
	GameState()
	{
		EventManager::listen(Event::Start, [](Event, void*) {
			started = true;
		});
		EventManager::listen(Event::Terminate, [](Event, void*) {
			started = false;
		});
	}
	static bool started;
	static GameState instance;
};
bool GameState::started;
GameState GameState::instance;
}

std::size_t MapleLink::activeLinkCount(int bus) const
{
	std::size_t count = 0;
	if (bus >= 0 && bus < (int)Links.size())
	{
		std::lock_guard<std::mutex> _(Mutex);
		for (const std::list<MapleLink::Ptr>& list : Links[bus]) {
			if (!list.empty() && list.front().get() == this)
				++count;
		}
	}
	return count;
}

bool MapleLink::isGameStarted() const {
	return GameState::started;
}

void MapleLink::registerLink(int bus, int port)
{
	if (bus >= 0 && bus < (int)Links.size()
			&& port >= 0 && port < (int)Links[0].size())
	{
		if (this->bus != bus) {
			this->bus = bus;
			this->ports = 0;
		}
		this->ports |= 1 << port;
		std::lock_guard<std::mutex> _(Mutex);
		Links[bus][port].push_front(shared_from_this());
	}
}
void MapleLink::unregisterLink(int bus, int port)
{
	if (bus >= 0 && bus < (int)Links.size()
			&& port >= 0 && port < (int)Links[0].size())
	{
		if (this->bus != bus) {
			this->bus = -1;
			this->ports = 0;
		}
		else {
			this->ports &= ~(1 << port);
		}
		std::lock_guard<std::mutex> _(Mutex);
		Links[bus][port].remove_if([this](const Ptr& item) { return item.get() == this; });
	}
}

bool MapleLink::StorageEnabled()
{
	std::lock_guard<std::mutex> _(Mutex);
	for (const auto& ports : Links)
	{
		for (const std::list<MapleLink::Ptr>& list : ports)
			if (!list.empty() && list.front()->storageEnabled())
				return true;
	}
	return false;
}

BaseMapleLink::BaseMapleLink(bool storageSupported)
	: storageSupported(storageSupported)
{
	EventManager::listen(Event::LoadState, eventHandler, this);
	EventManager::listen(Event::Start, eventHandler, this);
	EventManager::listen(Event::Terminate, eventHandler, this);
	if (isGameStarted())
		vmuStorage = storageSupported && config::UsePhysicalVmuMemory;
}

BaseMapleLink::~BaseMapleLink()
{
	EventManager::unlisten(Event::LoadState, eventHandler, this);
	EventManager::unlisten(Event::Start, eventHandler, this);
	EventManager::unlisten(Event::Terminate, eventHandler, this);
}

void BaseMapleLink::gameStarted() {
	vmuStorage = storageSupported && config::UsePhysicalVmuMemory && isConnected();
}

void BaseMapleLink::eventHandler(Event event, void *p)
{
	BaseMapleLink *self = (BaseMapleLink *)p;
	switch (event)
	{
	case Event::Start:
		self->gameStarted();
		break;
	case Event::Terminate:
		self->gameTermination();
		break;
	case Event::LoadState:
		if (self->vmuStorage) {
			WARN_LOG(INPUT, "State loaded but VMU has storage enabled");
			self->disableStorage();
		}
		break;
	default:
		break;
	}
}

void BaseMapleLink::disableStorage()
{
	if (!vmuStorage)
		return;
	vmuStorage = false;
	if (isGameStarted())
	{
		emu.run([bus=this->bus, ports=this->ports]() {
			if (bus != -1 && (ports & 1))
				maple_ReconnectDevice(bus, 0);
			if (bus != -1 && (ports & 2))
				maple_ReconnectDevice(bus, 1);
		});
	}
}

bool BaseMapleLink::storageEnabled()
{
	if (!isConnected())
		return false;
	if (!isGameStarted())
		return storageSupported && config::UsePhysicalVmuMemory;
	else
		return vmuStorage;
}

bool BaseMapleLink::handleGetLastError(const MapleMsg&)
{
	// Just acknowledge by default
	return true;
}
