#include "common/network/network_manager.hpp"

#include <algorithm>
#include <GLFW/glfw3.h>

#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(const UDPSocket& socket) :
	m_outgoing_packet_counter(0),
	m_incoming_packet_counter(0),
	m_outgoing_packets(),
	m_incoming_packets(),
	m_socket(socket)
{}

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()
{
	m_socket.connect(ip, port);
	// TODO: Handle actual connection with server
}

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, glfwGetTime(), 0});
		sort_packets(m_outgoing_packets);
	}
	else
		m_socket.send(m_socket, packet);
}

bool NetworkManager::recv_packet(NetworkPacket& packet) {
	if(m_socket.has_incoming_data()) {
		NetworkPacket temp;
		if(m_socket.recv(m_socket, temp)) {
			if(!(temp.get_flags() & NetworkPacketFlag::FLAG_RELIABLE)) {
				if(temp.get_id()==NetworkPacketID::PACKET_ACK) {
					uint32_t num = temp.read_uint32();
					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);
				}
				else {
					packet = temp;
					return true;
				}
			}
			if(m_incoming_packet_counter<=temp.get_num()) {
				m_incoming_packets.push_back(QueuedPacket{temp, glfwGetTime(), 0});
				sort_packets(m_incoming_packets);
			}
			else { // send ACK just in case
				LOG_VERBOSE("Reliable packet with num %d is outdated, dropping", temp.get_num());
				send_ack(temp.get_num());
			}
		}
	}

	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::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(glfwGetTime()-packet.m_last_retry>=NETWORK_MANAGER_PACKET_RETRY_INTERVAL) {
			LOG_VERBOSE("Resending reliable packet with num %d",packet.m_packet.get_num());
			m_socket.send(m_socket, packet.m_packet);
			packet.m_last_retry = glfwGetTime();
			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(glfwGetTime()-packet.m_last_retry>NETWORK_MANAGER_PACKET_RETRY_INTERVAL) {
			packet.m_last_retry = glfwGetTime();
			packet.m_retries++;
		}
	}
}

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

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();
	});
}
