/*
	Copyright 2022 flyinghead

	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/>.
 */
#pragma once
#include "types.h"
#include "net_platform.h"
#include "emulator.h"
#include "cfg/option.h"

#include <algorithm>
#include <vector>

class NetworkOutput
{
	void init()
	{
		if (!config::NetworkOutput || settings.naomi.slave || settings.naomi.drivingSimSlave == 1) {
			term();
			return;
		}
		if (server != INVALID_SOCKET)
			// already done
			return;
		server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		int option = 1;
		setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));

		sockaddr_in saddr{};
		socklen_t saddr_len = sizeof(saddr);
		saddr.sin_family = AF_INET;
		saddr.sin_addr.s_addr = INADDR_ANY;
		saddr.sin_port = htons(8000 + settings.naomi.drivingSimSlave);
		if (::bind(server, (sockaddr *)&saddr, saddr_len) < 0)
		{
			perror("Network output: bind failed");
			term();
			return;
		}
		if (listen(server, 5) < 0)
		{
			perror("Network output: listen failed");
			term();
			return;
		}
		set_non_blocking(server);
		EventManager::listen(Event::VBlank, vblankCallback, this);
	}

public:
	void term()
	{
		EventManager::unlisten(Event::VBlank, vblankCallback, this);
		for (sock_t sock : clients)
			closesocket(sock);
		clients.clear();
		if (server != INVALID_SOCKET) {
			closesocket(server);
			server = INVALID_SOCKET;
		}
	}

	void reset() {
		init();
		gameNameSent = false;
	}

	void output(const char *name, u32 value)
	{
		if (clients.empty())
			return;
		if (!gameNameSent)
		{
			send("game = " + settings.content.gameId + "\n");
			gameNameSent = true;
		}
		char s[9];
		snprintf(s, sizeof(s), "%x", value);
		std::string msg = std::string(name) + " = " + std::string(s) + "\n";	// mame uses \r
		send(msg);
	}

private:
	static void vblankCallback(Event event, void *param) {
		((NetworkOutput *)param)->acceptConnections();
	}

	void acceptConnections()
	{
		sockaddr_in src_addr{};
		socklen_t addr_len = sizeof(src_addr);
		sock_t sockfd = accept(server, (sockaddr *)&src_addr, &addr_len);
		if (sockfd != INVALID_SOCKET)
		{
			set_non_blocking(sockfd);
			if (gameNameSent)
			{
				std::string msg = "game = " + settings.content.gameId + "\n";
				if (::send(sockfd, msg.c_str(), msg.length(), 0) < 0)
				{
					int error = get_last_error();
					if (error != L_EWOULDBLOCK && error != L_EAGAIN) {
						closesocket(sockfd);
						return;
					}
				}
			}
			clients.push_back(sockfd);
		}
	}

	void send(const std::string& msg)
	{
		std::vector<sock_t> errorSockets;
		for (sock_t sock : clients)
			if (::send(sock, msg.c_str(), msg.length(), 0) < 0)
			{
				int error = get_last_error();
				if (error != L_EWOULDBLOCK && error != L_EAGAIN)
					errorSockets.push_back(sock);
			}
		for (sock_t sock : errorSockets)
		{
			closesocket(sock);
			clients.erase(std::find(clients.begin(), clients.end(), sock));
		}
	}

	sock_t server = INVALID_SOCKET;
	std::vector<sock_t> clients;
	bool gameNameSent = false;
};

extern NetworkOutput networkOutput;

