/*
    SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BTPEERMANAGER_H
#define BTPEERMANAGER_H

#include <interfaces/peersource.h>
#include <ktorrent_export.h>
#include <mse/encryptedpacketsocket.h>
#include <peer/peer.h>
#include <peer/peerconnector.h>
#include <peer/superseeder.h>

namespace KNetwork
{
class KResolverResults;
}

namespace bt
{
class PeerID;
class Piece;
class Torrent;
class Authenticate;
class ChunkCounter;
class PieceDownloader;
class ConnectionLimit;

using KNetwork::KResolverResults;

const Uint32 MAX_SIMULTANIOUS_AUTHS = 20;

/// Base class for handling pieces
class KTORRENT_EXPORT PieceHandler
{
public:
    virtual ~PieceHandler()
    {
    }

    virtual void pieceReceived(const Piece &p) = 0;
};

/**
 * @author Joris Guisson
 * @brief Manages all the Peers
 *
 * This class manages all Peer objects.
 * It can also open connections to other peers.
 */
class KTORRENT_EXPORT PeerManager : public QObject
{
    Q_OBJECT
public:
    /**
     * Constructor.
     * @param tor The Torrent
     */
    PeerManager(Torrent &tor);
    ~PeerManager() override;

    /// Get the connection limits
    static ConnectionLimit &connectionLimits();

    /**
     * Check for new connections, update down and upload speed of each Peer.
     * Initiate new connections.
     */
    void update();

    /**
     * Pause the peer connections
     */
    void pause();

    /**
     * Unpause the peer connections
     */
    void unpause();

    /**
     * Get a list of all peers.
     * @return A QList of Peer's
     */
    QList<Peer::Ptr> getPeers() const;

    /**
     * Find a Peer based on it's ID
     * @param peer_id The ID
     * @return A Peer or 0, if nothing could be found
     */
    Peer::Ptr findPeer(Uint32 peer_id);

    /**
     * Find a Peer based on it's PieceDownloader
     * @param pd The PieceDownloader
     * @return The matching Peer or 0 if none can be found
     */
    Peer::Ptr findPeer(PieceDownloader *pd);

    void setWantedChunks(const BitSet &bs);

    /**
     * Close all Peer connections.
     */
    void closeAllConnections();

    /**
     * Start listening to incoming requests.
     * @param superseed Set to true to get superseeding
     */
    void start(bool superseed);

    /**
     * Stop listening to incoming requests.
     */
    void stop();

    /**
     * Kill all peers who appear to be stale
     */
    void killStalePeers();

    /// Get the number of connected peers
    Uint32 getNumConnectedPeers() const;

    /// Get the number of connected seeders
    Uint32 getNumConnectedSeeders() const;

    /// Get the number of connected leechers
    Uint32 getNumConnectedLeechers() const;

    /// Get the number of pending peers we are attempting to connect to
    Uint32 getNumPending() const;

    /// Is the peer manager started
    bool isStarted() const;

    /// Get the Torrent
    const Torrent &getTorrent() const;

    /// Get the combined upload rate of all peers in bytes per sec
    Uint32 uploadRate() const;

    /**
     * A new connection is ready for this PeerManager.
     * @param sock The socket
     * @param peer_id The Peer's ID
     * @param support What extensions the peer supports
     */
    void newConnection(mse::EncryptedPacketSocket::Ptr sock, const PeerID &peer_id, Uint32 support);

    /**
     * Add a potential peer
     * @param addr The peers' address
     * @param local Is it a peer on the local network
     * @return void
     **/
    void addPotentialPeer(const net::Address &addr, bool local);

    /**
     * Kills all connections to seeders.
     * This is used when torrent download gets finished
     * and we should drop all connections to seeders
     */
    void killSeeders();

    /**
     * Kills all peers that are not interested for a long time.
     * This should be used when torrent is seeding ONLY.
     */
    void killUninterested();

    /// Get a BitSet of all available chunks
    const BitSet &getAvailableChunksBitSet() const;

    /// Get the chunk counter.
    ChunkCounter &getChunkCounter();

    /// Are we connected to a Peer given it's PeerID ?
    bool connectedTo(const PeerID &peer_id);

    /**
     * A peer has authenticated.
     * @param auth The Authenticate object
     * @param pcon The PeerConnector
     * @param ok Whether or not the attempt was succesfull
     * @param token The ConnectionLimit::Token
     */
    void peerAuthenticated(Authenticate *auth, PeerConnector::WPtr pcon, bool ok, ConnectionLimit::Token::Ptr token);

    /**
     * Save the IP's and port numbers of all peers.
     */
    void savePeerList(const QString &file);

    /**
     * Load the peer list again and add them to the potential peers
     */
    void loadPeerList(const QString &file);

    class PeerVisitor
    {
    public:
        virtual ~PeerVisitor()
        {
        }

        /// Called for each Peer
        virtual void visit(const Peer::Ptr p) = 0;
    };

    /// Visit all peers
    void visit(PeerVisitor &visitor);

    /// Is PEX enabled
    bool isPexEnabled() const;

    /// Enable or disable PEX
    void setPexEnabled(bool on);

    /// Set the group IDs of each peer
    void setGroupIDs(Uint32 up, Uint32 down);

    /// Have message received by a peer
    void have(Peer *p, Uint32 index);

    /// Bitset received by a peer
    void bitSetReceived(Peer *p, const BitSet &bs);

    /// Rerun the choker
    void rerunChoker();

    /// Does the choker need to run again
    bool chokerNeedsToRun() const;

    /// A PEX message was received
    void pex(const QByteArray &arr);

    /// A port packet was received
    void portPacketReceived(const QString &ip, Uint16 port);

    /// A Piece was received
    void pieceReceived(const Piece &p);

    /// Set the piece handler
    void setPieceHandler(PieceHandler *ph);

    /// Enable or disable super seeding
    void setSuperSeeding(bool on, const BitSet &chunks);

    /// Send a have message to all peers
    void sendHave(Uint32 index);

    /// Set if we are a partial seed or not
    void setPartialSeed(bool partial_seed);

    /// Are we a partial seed
    bool isPartialSeed() const;

public Q_SLOTS:
    /**
     * A PeerSource, has new potential peers.
     * @param ps The PeerSource
     */
    void peerSourceReady(PeerSource *ps);

Q_SIGNALS:
    void newPeer(Peer *p);
    void peerKilled(Peer *p);

private:
    class Private;
    Private *d;
};

}

#endif
