/*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_SERVER
#define DEF_SERVER

#include "Network/Host/ServerHost.hpp"
#include "Network/Message/Message.hpp"
#include "Fight/Referee.hpp"
#include "ArenaRotation.hpp"
#include "PeerList.hpp"
#include "PeerActivity.hpp"
#include "ServerFightController.hpp"
#include "Tools/Timer.hpp"
#include <enet/enet.h>
#include <set>

class ServerConfiguration;
class GameMetadata;
class ClientHandshakeMessage;
class PlayerActionMessage;
class Player;
class Matchmaking;

/// Encodes the current status of a server, i.e., whether:
/// - The server is in a lobby phase (awaiting players), or
/// - Enough players have joined and a game will start shortly, or
/// - A game is ongoing
class ServerStatus
{
    public:
        enum class Phase { InLobby, AwaitingGame, InGame, GameFinished };

        ServerStatus();

        void goToLobby(); ///< Returns to the lobby (not enough players)
        void queueGame(); ///< Start awaiting for an incoming game
        void startGame(); ///< Immediately start a game
        void finishGame(); ///< Notify that a game has finished

        /// Returns whether the server should start a game
        /// (AwaitingGame has been enabled for the required timeout)
        bool shouldStartGame() const;

        /// Returns whether the last game has been finished for a long enough
        /// time to start transitioning to the next game, or go back to lobby
        bool shouldTransition() const;

        /// Returns whether the server is in lobby mode
        bool isInLobby() const;

        /// Returns whether the server is awaiting the start of a new game
        bool isAwaitingGame() const;

        /// Returns whether the server is in game mode
        bool isInGame() const;

        /// Returns whether the server is in game completed mode
        bool hasGameFinished() const;

    private:
        Phase m_phase; ///< Current phase of the server
        /// Time at which \ref phase should turn from AwaitingGame to InGame
        Timer::TimePoint m_gameStart;
        /// Time at which \ref phase should turn from GameFinished to InLobby
        Timer::TimePoint m_gameTransition;
};

class Server : public ServerHostCallbackReceiver,
               public PeerListCallbackReceiver,
               public RefereeListener
{
    public:
        Server(ServerHost &host, const ServerConfiguration &serverConf);
        Server(const Server &copied) = delete;
        Server &operator=(const Server &copied) = delete;
        ~Server();

        void update(float frameTime);

        /// Server host callbacks
        void peerConnectedCallback(std::uint16_t peerID) override;
        void messageReceivedCallback(std::uint16_t peerID,
                                     const ENetPacket &packet,
                                     Channel channel) override;
        void peerDisconnectedCallback(std::uint16_t peerID) override;

        /// Peer list callbacks
        void playerAdded(const PeerData &peerData) override;
        void playerRemoved(std::size_t playerIx) override;
        void knownPeerJoined(std::uint16_t peerID,
                             const PeerData &peerData,
                             bool spectator) override;
        void knownPeerLeft(std::uint16_t peerID,
                           const PeerData &peerData,
                           bool spectator) override;
        void knownPeersUpdated() override;

        /// Referee instructions
        void readyRound(unsigned int roundId) override;
        void startRound() override;
        void roundOver(std::size_t winnerIx, const Player &winner,
                       std::uint8_t wonRounds) override;
        void roundOverDraw() override;
        void gameOver() override;

        /// Initialize enet and start a server instance
        static bool run(const ServerConfiguration &serverConf);

    private:
        void processHandshake(std::uint16_t peerID,
                              const ClientHandshakeMessage &message);
        void processPlayerAction(std::uint16_t peerID,
                                 const PlayerActionMessage &message);
        void processIllegalAction(std::uint16_t peerID);
        void processUnintelligible(std::uint16_t peerID);

        GameMetadata getGameMetadata() const;
        void broadcastGameState();
        void broadcastPings();
        void checkActivity();
        void queueGame();

        /// Returns the list of currently playing peers that control a KO player
        std::set<std::uint16_t> getKoPeers() const;

        ArenaRotation m_arena;
        ServerFightController m_fightController;
        PeerList m_peers;
        PeerActivity m_activity;
        Timer m_clock;

        /// Time at which the last set of player pings was broadcasted
        Timer::TimePoint m_lastSentPing;

        /// Time at which the last check of peer activity was performed
        Timer::TimePoint m_lastActivityCheck;

        /// Whether the game metadata was updated during the current iteration
        bool m_metadataUpdated;

        ServerStatus m_status;
        Referee m_referee;
        Matchmaking *m_matchmaking;

        ServerHost *m_host;
        const ServerConfiguration *m_serverConf;
};

#endif
