// Copyright 2011 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <atomic>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>

#include "Common/ChunkFile.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
#include "Core/HW/EXI_DeviceGecko.h"

u16                       GeckoSockServer::server_port;
int                       GeckoSockServer::client_count;
std::thread               GeckoSockServer::connectionThread;
std::atomic<bool>         GeckoSockServer::server_running;
std::mutex                GeckoSockServer::connection_lock;
std::queue<std::unique_ptr<sf::TcpSocket>> GeckoSockServer::waiting_socks;

GeckoSockServer::GeckoSockServer()
	: client_running(false)
{
	if (!connectionThread.joinable())
		connectionThread = std::thread(GeckoConnectionWaiter);
}

GeckoSockServer::~GeckoSockServer()
{
	if (clientThread.joinable())
	{
		--client_count;

		client_running.store(false);
		clientThread.join();
	}

	if (client_count <= 0)
	{
		server_running.store(false);
		connectionThread.join();
	}
}

void GeckoSockServer::GeckoConnectionWaiter()
{
	Common::SetCurrentThreadName("Gecko Connection Waiter");

	sf::TcpListener server;
	server_port = 0xd6ec; // "dolphin gecko"
	for (int bind_tries = 0; bind_tries <= 10 && !server_running.load(); bind_tries++)
	{
		server_running.store(server.listen(server_port) == sf::Socket::Done);
		if (!server_running.load())
			server_port++;
	}

	if (!server_running.load())
		return;

	Core::DisplayMessage(
		StringFromFormat("USBGecko: Listening on TCP port %u", server_port),
		5000);

	server.setBlocking(false);

	auto new_client = std::make_unique<sf::TcpSocket>();
	while (server_running.load())
	{
		if (server.accept(*new_client) == sf::Socket::Done)
		{
			std::lock_guard<std::mutex> lk(connection_lock);
			waiting_socks.push(std::move(new_client));

			new_client = std::make_unique<sf::TcpSocket>();
		}

		Common::SleepCurrentThread(1);
	}
}

bool GeckoSockServer::GetAvailableSock()
{
	bool sock_filled = false;

	std::lock_guard<std::mutex> lk(connection_lock);

	if (!waiting_socks.empty())
	{
		client = std::move(waiting_socks.front());
		if (clientThread.joinable())
		{
			client_running.store(false);
			clientThread.join();

			recv_fifo = std::deque<u8>();
			send_fifo = std::deque<u8>();
		}
		clientThread = std::thread(&GeckoSockServer::ClientThread, this);
		client_count++;
		waiting_socks.pop();
		sock_filled = true;
	}

	return sock_filled;
}

void GeckoSockServer::ClientThread()
{
	client_running.store(true);

	Common::SetCurrentThreadName("Gecko Client");

	client->setBlocking(false);

	while (client_running.load())
	{
		bool did_nothing = true;

		{
		std::lock_guard<std::mutex> lk(transfer_lock);

		// what's an ideal buffer size?
		char data[128];
		std::size_t got = 0;

		if (client->receive(&data[0], ArraySize(data), got) == sf::Socket::Disconnected)
			client_running.store(false);

		if (got != 0)
		{
			did_nothing = false;

			recv_fifo.insert(recv_fifo.end(), &data[0], &data[got]);
		}

		if (!send_fifo.empty())
		{
			did_nothing = false;

			std::vector<char> packet(send_fifo.begin(), send_fifo.end());
			send_fifo.clear();

			if (client->send(&packet[0], packet.size()) == sf::Socket::Disconnected)
				client_running.store(false);
		}
		} // unlock transfer

		if (did_nothing)
			Common::YieldCPU();
	}

	client->disconnect();
}

void CEXIGecko::ImmReadWrite(u32 &_uData, u32 _uSize)
{
	// We don't really care about _uSize
	(void)_uSize;

	if (!client || client->getLocalPort() == 0)
		GetAvailableSock();

	switch (_uData >> 28)
	{
	case CMD_LED_OFF:
		Core::DisplayMessage(
			"USBGecko: No LEDs for you!",
			3000);
		break;
	case CMD_LED_ON:
		Core::DisplayMessage(
			"USBGecko: A piercing blue light is now shining in your general direction",
			3000);
		break;

	case CMD_INIT:
		_uData = ident;
		break;

		// PC -> Gecko
		// |= 0x08000000 if successful
	case CMD_RECV:
		{
		std::lock_guard<std::mutex> lk(transfer_lock);
		if (!recv_fifo.empty())
		{
			_uData = 0x08000000 | (recv_fifo.front() << 16);
			recv_fifo.pop_front();
		}
		break;
		}

		// Gecko -> PC
		// |= 0x04000000 if successful
	case CMD_SEND:
		{
		std::lock_guard<std::mutex> lk(transfer_lock);
		send_fifo.push_back(_uData >> 20);
		_uData = 0x04000000;
		break;
		}

		// Check if ok for Gecko -> PC, or FIFO full
		// |= 0x04000000 if FIFO is not full
	case CMD_CHK_TX:
		_uData = 0x04000000;
		break;

		// Check if data in FIFO for PC -> Gecko, or FIFO empty
		// |= 0x04000000 if data in recv FIFO
	case CMD_CHK_RX:
		{
		std::lock_guard<std::mutex> lk(transfer_lock);
		_uData = recv_fifo.empty() ? 0 : 0x04000000;
		break;
		}

	default:
		ERROR_LOG(EXPANSIONINTERFACE, "Unknown USBGecko command %x", _uData);
		break;
	}
}
