/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD 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 OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
 */

/** @file network_turn.cpp TURN sending/receiving part of the network protocol. */

#include "../stdafx.h"

#include "../debug.h"
#include "network_coordinator.h"
#include "network_turn.h"

#include "../safeguards.h"

/** Connect to the TURN server. */
class NetworkTurnConnecter : public TCPConnecter {
private:
	ClientNetworkTurnSocketHandler *handler; ///< The callback for this attempt.

public:
	/**
	 * Initiate the connecting.
	 * @param handler Callback handler for denoting success or failure.
	 * @param connection_string The address of the TURN server.
	 */
	NetworkTurnConnecter(ClientNetworkTurnSocketHandler *handler, std::string_view connection_string) : TCPConnecter(connection_string, NETWORK_TURN_SERVER_PORT), handler(handler) {}

	void OnFailure() override
	{
		Debug(net, 9, "Turn::OnFailure()");

		this->handler->connecter = nullptr;

		this->handler->ConnectFailure();
	}

	void OnConnect(SOCKET s) override
	{
		Debug(net, 9, "Turn::OnConnect()");

		this->handler->connecter = nullptr;

		this->handler->sock = s;
	}
};

bool ClientNetworkTurnSocketHandler::ReceiveServerError(Packet &)
{
	Debug(net, 9, "ReceiveServerError()");

	this->ConnectFailure();

	return false;
}

bool ClientNetworkTurnSocketHandler::ReceiveServerConnected(Packet &p)
{
	std::string hostname = p.Recv_string(NETWORK_HOSTNAME_LENGTH);
	Debug(net, 9, "Turn::ReceiveServerConnected({})", hostname);

	/* Act like we no longer have a socket, as we are handing it over to the
	 * game handler. */
	SOCKET game_sock = this->sock;
	this->sock = INVALID_SOCKET;

	NetworkAddress address = NetworkAddress(hostname, NETWORK_DEFAULT_PORT);
	_network_coordinator_client.ConnectSuccess(this->token, game_sock, address);

	return false;
}

/**
 * Connect to the TURN server.
 */
void ClientNetworkTurnSocketHandler::Connect()
{
	Debug(net, 9, "Turn::Connect()");

	this->connect_started = true;
	this->connecter = TCPConnecter::Create<NetworkTurnConnecter>(this, this->connection_string);
}

/**
 * Prepare a TURN connection.
 * Not until you run Connect() on the resulting instance will it start setting
 * up the TURN connection.
 * @param token The token as received from the Game Coordinator.
 * @param tracking_number The tracking number as received from the Game Coordinator.
 * @param ticket The ticket as received from the Game Coordinator.
 * @param connection_string Connection string of the TURN server.
 * @return The handler for this TURN connection.
 */
/* static */ std::unique_ptr<ClientNetworkTurnSocketHandler> ClientNetworkTurnSocketHandler::Turn(std::string_view token, uint8_t tracking_number, std::string_view ticket, std::string_view connection_string)
{
	auto turn_handler = std::make_unique<ClientNetworkTurnSocketHandler>(token, tracking_number, connection_string);

	auto p = std::make_unique<Packet>(turn_handler.get(), PacketTurnType::ClientConnect);
	p->Send_uint8(NETWORK_COORDINATOR_VERSION);
	p->Send_string(ticket);

	Debug(net, 9, "Turn::SendTurn({})", ticket);
	turn_handler->SendPacket(std::move(p));

	return turn_handler;
}

/** Callback when we were unable to connect. */
void ClientNetworkTurnSocketHandler::ConnectFailure()
{
	_network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
}

NetworkRecvStatus ClientNetworkTurnSocketHandler::CloseConnection(bool error)
{
	NetworkTurnSocketHandler::CloseConnection(error);

	/* Also make sure any pending connecter is killed ASAP. */
	if (this->connecter != nullptr) {
		this->connecter->Kill();
		this->connecter = nullptr;
	}

	return NetworkRecvStatus::Okay;
}

/** Stop the attempt to connect. */
ClientNetworkTurnSocketHandler::~ClientNetworkTurnSocketHandler()
{
	if (this->connecter != nullptr) {
		this->connecter->Kill();
		this->connecter = nullptr;
	}
}

/**
 * Check whether we received/can send some data from/to the TURN server and
 * when that's the case handle it appropriately
 */
void ClientNetworkTurnSocketHandler::SendReceive()
{
	if (this->sock == INVALID_SOCKET) return;

	if (this->CanSendReceive()) {
		this->ReceivePackets();
	}

	this->SendPackets();
}
