/*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_MATCHMAKING
#define DEF_MATCHMAKING

#include "Network/Serialization/MatchmakingStrategy.hpp" ///Used in allocate()
#include <random>
#include <vector>
#include <cstddef>

/// A description of the changes applied by a matchmaking strategy:
/// Which players are moved to spectators and
/// which spectators are moved to players
struct MatchmakingAssignment
{
    MatchmakingAssignment() = default;

    MatchmakingAssignment(std::vector<std::size_t> &&toSpectators,
                          std::vector<std::size_t> &&toPlayers) :
    becomesSpectator(toSpectators), becomesPlayer(toPlayers) {}

    /// Verifies that the assignment is valid
    /// (enough playing peers and no invalid/duplicated indices)
    bool validate(std::size_t playerLimit,
                  std::size_t playerCount,
                  std::size_t spectatorCount) const;

    /// List of players that will become spectators
    /// (players identified by their index in PeerList::m_players)
    std::vector<std::size_t> becomesSpectator;

    /// List of spectators that will become players
    /// (spectators identified by their index in PeerList::m_spectators)
    std::vector<std::size_t> becomesPlayer;
};

/// Handles matchmaking at the end of a game:
/// Who should be playing and spectating?
class Matchmaking
{
    public:
        virtual ~Matchmaking() = default;

        /// Provides information about the last winning player ix to
        /// matchmaking strategies that may need it to reassign peers
        virtual void setLastWinner(std::size_t lastWinnerIx) = 0;

        /// Returns a reassignment of the current peers based on
        /// - The expected number of players to start a game \p playerLimit,
        /// - The actual current number of players \p playerCount,
        /// - The current number of spectators \p spectatorCount
        virtual MatchmakingAssignment reassign(
            std::size_t playerLimit,
            std::size_t playerCount,
            std::size_t spectatorCount) const = 0;

        static Matchmaking *allocate(MatchmakingStrategy::Type strategy);
};

/// A matchmaking strategy that maintains the order of peers:
/// Playing peers will remain players until they disconnect, at which point
/// they are replaced by the earliest connected spectators
class StackMatchmaking : public Matchmaking
{
    public:
        MatchmakingAssignment reassign(
            std::size_t playerLimit,
            std::size_t playerCount,
            std::size_t spectatorCount) const override;

        //Last winner has no impact on stack matchmaking
        void setLastWinner(std::size_t) override {}
};

/// A matchmaking strategy that randomly shuffles
/// all connected peers to player and spectator slots
class RandomMatchmaking : public Matchmaking
{
    public:
        RandomMatchmaking();
        MatchmakingAssignment reassign(
            std::size_t playerLimit,
            std::size_t playerCount,
            std::size_t spectatorCount) const override;

        //Last winner has no impact on random matchmaking
        void setLastWinner(std::size_t) override {}

    private:
        mutable std::mt19937 m_generator;
};

class ChallengerMatchmaking : public Matchmaking
{
    public:
        ChallengerMatchmaking();
        void setLastWinner(std::size_t lastWinnerIx) override;

        MatchmakingAssignment reassign(
            std::size_t playerLimit,
            std::size_t playerCount,
            std::size_t spectatorCount) const override;

    private:
        std::size_t m_lastWinnerIx;
        mutable std::mt19937 m_generator;
};

#endif
