/*
    SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#ifndef KTQUEUEMANAGER_H
#define KTQUEUEMANAGER_H

#include <set>

#include <KSharedConfig>
#include <QObject>

#include <interfaces/queuemanagerinterface.h>
#include <interfaces/torrentinterface.h>
#include <ktcore_export.h>

namespace bt
{
class SHA1Hash;
struct TrackerTier;
class WaitJob;
}

namespace kt
{
class KTCORE_EXPORT QueuePtrList : public QList<bt::TorrentInterface *>
{
public:
    QueuePtrList();
    ~QueuePtrList();

    /**
     * Sort based upon priority
     */
    void sort();

protected:
    static bool biggerThan(bt::TorrentInterface *tc1, bt::TorrentInterface *tc2);
};

/**
 * @author Ivan Vasic
 * @brief This class contains list of all TorrentControls and is responsible for starting/stopping them
 */
class KTCORE_EXPORT QueueManager : public QObject, public bt::QueueManagerInterface
{
    Q_OBJECT

public:
    QueueManager();
    ~QueueManager() override;

    void append(bt::TorrentInterface *tc);
    void remove(bt::TorrentInterface *tc);
    void clear();

    /**
        Save the state of the QueueManager
        @param cfg The config
    */
    void saveState(KSharedConfigPtr cfg);

    /**
        Load the state of the QueueManager
        @param cfg The config
    */
    void loadState(KSharedConfigPtr cfg);

    /**
     * Check if we need to decrease the priority of stalled torrents
     * @param min_stall_time Stall time in minutes
     * @param now The current time
     */
    void checkStalledTorrents(bt::TimeStamp now, bt::Uint32 min_stall_time);

    /**
     * Start a torrent
     * @param tc The torrent
     * @return What happened
     */
    bt::TorrentStartResponse start(bt::TorrentInterface *tc);

    /**
     * Stop a torrent
     * @param tc The torrent
     */
    void stop(bt::TorrentInterface *tc);

    /**
     * Start a list of torrents.
     * @param todo The list of torrents
     */
    void start(QList<bt::TorrentInterface *> &todo);

    /**
     * Stop a list of torrents
     * @param todo The list of torrents
     */
    void stop(QList<bt::TorrentInterface *> &todo);

    /// Stop all torrents
    void stopAll();

    /// Start all torrents
    void startAll();

    /**
     * Stop all running torrents
     * @param wjob WaitJob which waits for stopped events to reach the tracker
     */
    void onExit(bt::WaitJob *wjob);

    /// Get the number of torrents
    int count()
    {
        return downloads.count();
    }

    /// Get the number of downloads
    int countDownloads();

    /// Get the number of seeds
    int countSeeds();

    enum Flags {
        SEEDS = 1,
        DOWNLOADS = 2,
        ALL = 3,
    };

    /**
     * Get the number of running torrents
     * @param flags Which torrents to choose
     */
    int getNumRunning(Flags flags = ALL);

    /**
     * Start the next torrent.
     */
    void startNext();

    /**
        If the QM is disabled this function needs to be called to start
        all the torrents which were running at the time of the previous exit.
    */
    void startAutoStartTorrents();

    typedef QList<bt::TorrentInterface *>::iterator iterator;
    typedef QList<bt::TorrentInterface *>::const_iterator const_iterator;

    iterator begin();
    iterator end();

    const_iterator begin() const;
    const_iterator end() const;

    /**
     * Get the torrent at index idx in the list.
     * @param idx Index of the torrent
     * @return The torrent or 0 if the index is out of bounds
     */
    const bt::TorrentInterface *getTorrent(bt::Uint32 idx) const;

    /**
     * Get the torrent at index idx in the list.
     * @param idx Index of the torrent
     * @return The torrent or 0 if the index is out of bounds
     */
    bt::TorrentInterface *getTorrent(bt::Uint32 idx);

    /**
     * See if we already loaded a torrent.
     * @param ih The info hash of a torrent
     * @return true if we do, false if we don't
     */
    bool alreadyLoaded(const bt::SHA1Hash &ih) const override;

    /**
     * Merge announce lists to a torrent
     * @param ih The info_hash of the torrent to merge to
     * @param trk First tier of trackers
     */
    void mergeAnnounceList(const bt::SHA1Hash &ih, const bt::TrackerTier *trk) override;

    /**
     * Requested by each TorrentControl during its update to
     * get permission on saving Stats file to disk. May be
     * overriden to balance I/O operations.
     * @param tc Pointer to TorrentControl instance
     * @return true if file save is permitted, false otherwise
     */

    bool permitStatsSync(bt::TorrentControl *tc) override;

    /**
     * Set the maximum number of downloads
     * @param m Max downloads
     */
    void setMaxDownloads(int m);

    /**
     * Set the maximum number of seeds
     * @param m Max seeds
     */
    void setMaxSeeds(int m);

    /**
     * Enable or disable keep seeding (after a torrent has finished)
     * @param ks Keep seeding
     */
    void setKeepSeeding(bool ks);

    /**
     * Sets global suspended state for QueueManager and stopps all running torrents.
     * No torrents will be automatically started/stopped with QM.
     */
    void setSuspendedState(bool suspend);

    /// Get the suspended state
    bool getSuspendedState() const
    {
        return suspended_state;
    }

    /**
     * Reindex the queue priorities.
     */
    void reindexQueue();

    /**
     * Check if a torrent has file conflicts with other torrents.
     * If conflicting are found, a list of names of the conflicting torrents is filled in.
     * @param tc The torrent
     * @param conflicting List of conflicting torrents
     */
    bool checkFileConflicts(bt::TorrentInterface *tc, QStringList &conflicting) const;

    /**
     * Places all torrents from downloads in the right order in queue.
     * Use this when torrent priorities get changed
     */
    void orderQueue();

Q_SIGNALS:
    /**
     * User tried to enqueue a torrent that has reached max share ratio. It's not possible.
     * Signal should be connected to SysTray slot which shows appropriate KPassivePopup info.
     * @param tc The torrent in question.
     */
    void queuingNotPossible(bt::TorrentInterface *tc);

    /**
     * Diskspace is running low.
     * Signal should be connected to SysTray slot which shows appropriate KPassivePopup info.
     * @param tc The torrent in question.
     */
    void lowDiskSpace(bt::TorrentInterface *tc, bool stopped);

    /// Emitted before the queue is reordered
    void orderingQueue();

    /**
     * Emitted when the QM has reordered it's queue
     */
    void queueOrdered();

    /**
     * Emitted when the suspended state changes.
     * @param suspended The suspended state
     */
    void suspendStateChanged(bool suspended);

public Q_SLOTS:
    void torrentFinished(bt::TorrentInterface *tc);
    void torrentAdded(bt::TorrentInterface *tc, bool start_torrent);
    void torrentRemoved(bt::TorrentInterface *tc);
    void torrentsRemoved(QList<bt::TorrentInterface *> &tors);
    void torrentStopped(bt::TorrentInterface *tc);
    void onLowDiskSpace(bt::TorrentInterface *tc, bool toStop);
    void onOnlineStateChanged(bool);

private:
    void startSafely(bt::TorrentInterface *tc);
    void stopSafely(bt::TorrentInterface *tc, bt::WaitJob *wjob = nullptr);
    void checkDiskSpace(QList<bt::TorrentInterface *> &todo);
    void checkMaxSeedTime(QList<bt::TorrentInterface *> &todo);
    void checkMaxRatio(QList<bt::TorrentInterface *> &todo);
    void rearrangeQueue();
    bt::TorrentStartResponse startInternal(bt::TorrentInterface *tc);
    bool checkLimits(bt::TorrentInterface *tc, bool interactive);
    bool checkDiskSpace(bt::TorrentInterface *tc, bool interactive);

private:
    QueuePtrList downloads;
    std::set<bt::TorrentInterface *> suspended_torrents;
    int max_downloads;
    int max_seeds;
    bool suspended_state;
    bool keep_seeding;
    bool exiting;
    bool ordering;
    QDateTime network_down_time;
    bt::TimeStamp last_stats_sync_permitted;
};
}
#endif
