/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu 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 3 of the License, or
(at your option) any later version.

Hikou no mizu 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 Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef DEF_PEER_LIST
#define DEF_PEER_LIST

#include "Network/Serialization/PeerData.hpp" ///< PeerData and PeerRole
#include "Tools/Timer.hpp"
#include <map>
#include <vector>
#include <cstdint>

class Matchmaking;

/// Associates a peer data with its unique identifier
struct ConnectedPeer
{
    ConnectedPeer(std::uint16_t id, const PeerData &data) :
    peerID(id), peerData(data) {}

    std::uint16_t peerID;
    PeerData peerData;
};

/// Receiver to listen to peer list change events and react accordingly
class PeerListCallbackReceiver
{
    public:
        virtual ~PeerListCallbackReceiver() = default;

        /// Called when a playing peer was added
        /// (either after a peer joined, or after matchmaking)
        virtual void playerAdded(const PeerData &peerData) = 0;

        /// Called when a playing peer was removed
        /// (either after a peer left, or after matchmaking)
        virtual void playerRemoved(std::size_t playerIx) = 0;

        /// Called when any (player/spectator) peer has joined
        /// (only for player/spectator first connection)
        virtual void knownPeerJoined(std::uint16_t peerID,
                                     const PeerData &peerData,
                                     bool spectator) = 0;

        /// Called when any (player/spectator) peer has joined
        virtual void knownPeerLeft(std::uint16_t peerID,
                                   const PeerData &peerData,
                                   bool spectator) = 0;

        /// Called when the known peer list was updated: Any change in either
        /// known players or spectators, including matchmaking
        virtual void knownPeersUpdated() = 0;
};

/// Handles a fight hosted by the server, with a fight scene and a referee.
/// Receives player actions and produces fight status messages
class PeerList
{
    public:
        PeerList(std::uint8_t playerLimit, std::size_t spectatorLimit,
                 const std::vector<std::string> &validCharacters,
                 PeerListCallbackReceiver &receiver);

        /// Declares that a peer has connected, returns false if the number of
        /// known peers is at maximum capacity (playerLimit + spectatorLimit)
        bool addPeer(std::uint16_t peerID);

        /// Declares that a peer has disconnected
        void removePeer(std::uint16_t peerID);

        /// Reassign peers according to a matchmaking strategy
        bool reassignPeers(const Matchmaking &matchmaking);

        /// Possible results when a peer introduces itself
        enum class Introduction
        {
            Success,
            Unexpected,
            CharacterUnknown,
            ServerIsFull
        };

        /// Attempts to introduce a peer and register it either as a player
        /// or spectator, returns whether it was successful or the reason if not
        Introduction introducePeer(std::uint16_t peerID,
                                   const PeerData &peerData, bool inLobby);

        /// Returns a list of unknown peers (which have not sent a handshake)
        /// that have been connected for longer than \p duration
        std::vector<std::uint16_t> notIntroducedFor(
            const std::chrono::seconds &duration) const;

        bool getRole(std::uint16_t peerID, PeerRole &dstRole) const;
        bool getPlayerData(std::uint16_t peerID, PeerData &dstData) const;

        const std::vector<ConnectedPeer> &getPlayers() const;
        const std::vector<ConnectedPeer> &getSpectators() const;

        std::vector<PeerData> getPlayersData() const;
        std::vector<PeerData> getSpectatorsData() const;

        /// Returns whether there are enough playing peers to start a game
        bool playersReady() const;

        /// Returns whether there is at least one free playing slot
        /// and one spectator that could start playing
        bool spectatorsAvailable() const;

    private:
        /// List of just-connected peer id that did not yet send a handshake,
        /// associated with their connection time to kick any
        /// peer still unintroduced after a timeout
        std::map<std::uint16_t, Timer::TimePoint> m_unknownPeers;

        std::vector<ConnectedPeer> m_players;
        std::vector<ConnectedPeer> m_spectators;

        const std::vector<std::string> m_validCharacters;
        const std::size_t m_playerLimit;
        const std::size_t m_spectatorLimit;

        PeerListCallbackReceiver *m_receiver;
};

#endif
