/*
PolyGun

Copyright (c) 2023 mrkubax10 <mrkubax10@onet.pl>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "common/network/network_manager.hpp"

#include <algorithm>

#include "common/logger.hpp"

using namespace polygun::network;

static const double NETWORK_MANAGER_PACKET_RETRY_INTERVAL = 1;
static const uint8_t NETWORK_MANAGER_MAX_RETRY_COUNT = 10;

NetworkManager::NetworkManager(std::shared_ptr<UDPSocket> socket, const NetworkEndpoint& endpoint) :
	m_outgoing_packet_counter(0),
	m_incoming_packet_counter(0),
	m_outgoing_packets(),
	m_incoming_packets(),
	m_socket(socket),
	m_endpoint(endpoint)
{}

NetworkManager::NetworkManager(const std::string& ip, unsigned short port) :
	m_outgoing_packet_counter(0),
	m_incoming_packet_counter(0),
	m_outgoing_packets(),
	m_incoming_packets(),
	m_socket(new UDPSocket),
	m_endpoint()
{
	m_socket->connect(ip, port);
	m_endpoint = m_socket->get_endpoint();
}

NetworkPacket NetworkManager::create_packet(uint8_t flags, uint8_t id) {
	return NetworkPacket(flags, id, (flags & NetworkPacketFlag::FLAG_RELIABLE) ? m_outgoing_packet_counter++ : 0);
}

void NetworkManager::send_packet(const NetworkPacket& packet) {
	if(packet.get_flags() & NetworkPacketFlag::FLAG_RELIABLE) {
		m_outgoing_packets.push_back(QueuedPacket{packet, -10, 0});
		sort_packets(m_outgoing_packets);
	}
	else
		m_socket->send(packet, m_endpoint);
}

bool NetworkManager::recv_packet(NetworkPacket& packet) {
	if(m_incoming_packets.size()>0 && m_incoming_packets.back().m_packet.get_num()==m_incoming_packet_counter) {
		packet = m_incoming_packets.back().m_packet;
		m_incoming_packets.pop_back();
		send_ack(m_incoming_packet_counter++);
		return true;
	}

	return false;
}

void NetworkManager::push_packet(NetworkPacket& packet) {
	if(!(packet.get_flags() & NetworkPacketFlag::FLAG_RELIABLE) && packet.get_id()==NetworkPacketID::PACKET_ACK) {
		uint32_t num;
		packet.read(num);
		LOG_VERBOSE("Received ACK of reliable packet with num %d", num);
		std::vector<QueuedPacket>::iterator it = std::find_if(m_outgoing_packets.begin(), m_outgoing_packets.end(), [num](const QueuedPacket& queued_packet) {
			return queued_packet.m_packet.get_num()==num;
		});
		if(it!=m_outgoing_packets.end())
			m_outgoing_packets.erase(it);
		return;
	}

	if(m_incoming_packet_counter<=packet.get_num()) {
		m_incoming_packets.push_back(QueuedPacket{packet, time(0), 0});
		sort_packets(m_incoming_packets);
	}
	else { // send ACK just in case
		LOG_VERBOSE("Reliable packet with num %d is outdated, dropping", packet.get_num());
		send_ack(packet.get_num());
	}
}

void NetworkManager::send_ack(uint32_t packet_num) const {
	NetworkPacket packet(NetworkPacketFlag::FLAG_NONE, NetworkPacketID::PACKET_ACK);
	packet.write(packet_num);
	m_socket->send(packet, m_endpoint);
}

void NetworkManager::update() {
	for(size_t i = 0; i<m_outgoing_packets.size(); i++) {
		QueuedPacket& packet = m_outgoing_packets[i];
		if(packet.m_retries>=NETWORK_MANAGER_MAX_RETRY_COUNT) {
			LOG_ERROR("Max retry count exceeded, dropping outgoing packet with num %d", packet.m_packet.get_num());
			m_outgoing_packets.erase(m_outgoing_packets.begin()+i);
			i--;
			continue;
		}
		if(time(0)-packet.m_last_retry>=NETWORK_MANAGER_PACKET_RETRY_INTERVAL) {
			if(packet.m_last_retry>0)
				LOG_VERBOSE("Resending reliable packet with num %d",packet.m_packet.get_num());
			m_socket->send(packet.m_packet, m_endpoint);
			packet.m_last_retry = time(0);
			packet.m_retries++;
		}
	}

	if(m_incoming_packets.size()>0) {
		QueuedPacket& packet = m_incoming_packets.back();
		if(packet.m_retries>=NETWORK_MANAGER_MAX_RETRY_COUNT) {
			LOG_ERROR("Connection broken, lost packet with num %d exceeded max retry count", m_incoming_packet_counter);
			// TODO: Quit from game
			return;
		}
		if(time(0)-packet.m_last_retry>NETWORK_MANAGER_PACKET_RETRY_INTERVAL) {
			packet.m_last_retry = time(0);
			packet.m_retries++;
		}
	}
}

void NetworkManager::sort_packets(std::vector<QueuedPacket>& packets) {
	std::stable_sort(packets.begin(), packets.end(), [](const NetworkManager::QueuedPacket& lhs, const NetworkManager::QueuedPacket& rhs) {
		return lhs.m_packet.get_num()<rhs.m_packet.get_num();
	});
}
