/*
 * Bittorrent Client using Qt and libtorrent.
 * Copyright (C) 2015  Vladimir Golovnev <glassez@yandex.ru>
 * Copyright (C) 2006  Christophe Dumez <chris@qbittorrent.org>
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link this program with the OpenSSL project's "OpenSSL" library (or with
 * modified versions of it that use the same license as the "OpenSSL" library),
 * and distribute the linked executables. You must obey the GNU General Public
 * License in all respects for all of the code used other than "OpenSSL".  If you
 * modify file(s), you may extend this exception to your version of the file(s),
 * but you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 */

#pragma once

#include <functional>

#include <libtorrent/fwd.hpp>
#include <libtorrent/torrent_handle.hpp>
#include <libtorrent/torrent_status.hpp>

#include <QDateTime>
#include <QHash>
#include <QObject>
#include <QQueue>
#include <QSet>
#include <QString>
#include <QVector>

#include "private/speedmonitor.h"
#include "infohash.h"
#include "torrenthandle.h"
#include "torrentinfo.h"

namespace BitTorrent
{
    class Session;
    struct AddTorrentParams;

    struct CreateTorrentParams
    {
        CreateTorrentParams() = default;
        explicit CreateTorrentParams(const AddTorrentParams &params);

        // for both new and restored torrents
        QString name;
        QString category;
        QSet<QString> tags;
        QString savePath;
        int uploadLimit = -1;
        int downloadLimit = -1;
        bool disableTempPath = false;
        bool sequential = false;
        bool firstLastPiecePriority = false;
        bool hasSeedStatus = false;
        bool skipChecking = false;
        bool hasRootFolder = true;
        bool forced = false;
        bool paused = false;
        bool restored = false;  // is existing torrent job?

        // for new torrents
        QVector<DownloadPriority> filePriorities;
        QDateTime addedTime;

        // for restored torrents
        qreal ratioLimit = TorrentHandle::USE_GLOBAL_RATIO;
        int seedingTimeLimit = TorrentHandle::USE_GLOBAL_SEEDING_TIME;
    };

    enum class MoveStorageMode
    {
        KeepExistingFiles,
        Overwrite
    };

    class TorrentHandleImpl final : public QObject, public TorrentHandle
    {
        Q_DISABLE_COPY(TorrentHandleImpl)
        Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentHandleImpl)

    public:
        TorrentHandleImpl(Session *session, const lt::torrent_handle &nativeHandle,
                          const CreateTorrentParams &params);
        ~TorrentHandleImpl() override;

        bool isValid() const;

        InfoHash hash() const override;
        QString name() const override;
        QDateTime creationDate() const override;
        QString creator() const override;
        QString comment() const override;
        bool isPrivate() const override;
        qlonglong totalSize() const override;
        qlonglong wantedSize() const override;
        qlonglong completedSize() const override;
        qlonglong incompletedSize() const override;
        qlonglong pieceLength() const override;
        qlonglong wastedSize() const override;
        QString currentTracker() const override;

        QString savePath(bool actual = false) const override;
        QString rootPath(bool actual = false) const override;
        QString contentPath(bool actual = false) const override;

        bool useTempPath() const override;

        bool isAutoTMMEnabled() const override;
        void setAutoTMMEnabled(bool enabled) override;
        QString category() const override;
        bool belongsToCategory(const QString &category) const override;
        bool setCategory(const QString &category) override;

        QSet<QString> tags() const override;
        bool hasTag(const QString &tag) const override;
        bool addTag(const QString &tag) override;
        bool removeTag(const QString &tag) override;
        void removeAllTags() override;

        bool hasRootFolder() const override;

        int filesCount() const override;
        int piecesCount() const override;
        int piecesHave() const override;
        qreal progress() const override;
        QDateTime addedTime() const override;
        qreal ratioLimit() const override;
        int seedingTimeLimit() const override;

        QString filePath(int index) const override;
        QString fileName(int index) const override;
        qlonglong fileSize(int index) const override;
        QStringList absoluteFilePaths() const override;
        QStringList absoluteFilePathsUnwanted() const override;
        QVector<DownloadPriority> filePriorities() const override;

        TorrentInfo info() const override;
        bool isSeed() const override;
        bool isPaused() const override;
        bool isResumed() const override;
        bool isQueued() const override;
        bool isForced() const override;
        bool isChecking() const override;
        bool isDownloading() const override;
        bool isUploading() const override;
        bool isCompleted() const override;
        bool isActive() const override;
        bool isInactive() const override;
        bool isErrored() const override;
        bool isSequentialDownload() const override;
        bool hasFirstLastPiecePriority() const override;
        TorrentState state() const override;
        bool hasMetadata() const override;
        bool hasMissingFiles() const override;
        bool hasError() const override;
        bool hasFilteredPieces() const override;
        int queuePosition() const override;
        QVector<TrackerEntry> trackers() const override;
        QHash<QString, TrackerInfo> trackerInfos() const override;
        QVector<QUrl> urlSeeds() const override;
        QString error() const override;
        qlonglong totalDownload() const override;
        qlonglong totalUpload() const override;
        qlonglong activeTime() const override;
        qlonglong finishedTime() const override;
        qlonglong seedingTime() const override;
        qlonglong eta() const override;
        QVector<qreal> filesProgress() const override;
        int seedsCount() const override;
        int peersCount() const override;
        int leechsCount() const override;
        int totalSeedsCount() const override;
        int totalPeersCount() const override;
        int totalLeechersCount() const override;
        int completeCount() const override;
        int incompleteCount() const override;
        QDateTime lastSeenComplete() const override;
        QDateTime completedTime() const override;
        qlonglong timeSinceUpload() const override;
        qlonglong timeSinceDownload() const override;
        qlonglong timeSinceActivity() const override;
        int downloadLimit() const override;
        int uploadLimit() const override;
        bool superSeeding() const override;
        QVector<PeerInfo> peers() const override;
        QBitArray pieces() const override;
        QBitArray downloadingPieces() const override;
        QVector<int> pieceAvailability() const override;
        qreal distributedCopies() const override;
        qreal maxRatio() const override;
        int maxSeedingTime() const override;
        qreal realRatio() const override;
        int uploadPayloadRate() const override;
        int downloadPayloadRate() const override;
        qlonglong totalPayloadUpload() const override;
        qlonglong totalPayloadDownload() const override;
        int connectionsCount() const override;
        int connectionsLimit() const override;
        qlonglong nextAnnounce() const override;
        QVector<qreal> availableFileFractions() const override;

        void setName(const QString &name) override;
        void setSequentialDownload(bool enable) override;
        void setFirstLastPiecePriority(bool enabled) override;
        void pause() override;
        void resume(bool forced = false) override;
        void move(QString path) override;
        void forceReannounce(int index = -1) override;
        void forceDHTAnnounce() override;
        void forceRecheck() override;
        void renameFile(int index, const QString &name) override;
        void prioritizeFiles(const QVector<DownloadPriority> &priorities) override;
        void setRatioLimit(qreal limit) override;
        void setSeedingTimeLimit(int limit) override;
        void setUploadLimit(int limit) override;
        void setDownloadLimit(int limit) override;
        void setSuperSeeding(bool enable) override;
        void flushCache() const override;
        void addTrackers(const QVector<TrackerEntry> &trackers) override;
        void replaceTrackers(const QVector<TrackerEntry> &trackers) override;
        void addUrlSeeds(const QVector<QUrl> &urlSeeds) override;
        void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
        bool connectPeer(const PeerAddress &peerAddress) override;

        QString createMagnetURI() const override;

        bool needSaveResumeData() const;

        // Session interface
        lt::torrent_handle nativeHandle() const;

        void handleAlert(const lt::alert *a);
        void handleStateUpdate(const lt::torrent_status &nativeStatus);
        void handleTempPathChanged();
        void handleCategorySavePathChanged();
        void handleAppendExtensionToggled();
        void saveResumeData();
        void handleStorageMoved(const QString &newPath, const QString &errorMessage);

    private:
        typedef std::function<void ()> EventTrigger;

#if (LIBTORRENT_VERSION_NUM < 10200)
        using LTFileIndex = int;
#else
        using LTFileIndex = lt::file_index_t;
#endif

        void updateStatus();
        void updateStatus(const lt::torrent_status &nativeStatus);
        void updateState();
        void updateTorrentInfo();

        void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p);
        void handleFileCompletedAlert(const lt::file_completed_alert *p);
        void handleFileRenamedAlert(const lt::file_renamed_alert *p);
        void handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p);
        void handleMetadataReceivedAlert(const lt::metadata_received_alert *p);
        void handlePerformanceAlert(const lt::performance_alert *p) const;
        void handleSaveResumeDataAlert(const lt::save_resume_data_alert *p);
        void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p);
        void handleTorrentCheckedAlert(const lt::torrent_checked_alert *p);
        void handleTorrentFinishedAlert(const lt::torrent_finished_alert *p);
        void handleTorrentPausedAlert(const lt::torrent_paused_alert *p);
        void handleTorrentResumedAlert(const lt::torrent_resumed_alert *p);
        void handleTrackerErrorAlert(const lt::tracker_error_alert *p);
        void handleTrackerReplyAlert(const lt::tracker_reply_alert *p);
        void handleTrackerWarningAlert(const lt::tracker_warning_alert *p);

        void resume_impl(bool forced);
        bool isMoveInProgress() const;
        QString actualStorageLocation() const;
        bool isAutoManaged() const;
        void setAutoManaged(bool enable);

        void adjustActualSavePath();
        void adjustActualSavePath_impl();
        void move_impl(QString path, MoveStorageMode mode);
        void moveStorage(const QString &newPath, MoveStorageMode mode);
        void manageIncompleteFiles();
        void setFirstLastPiecePriorityImpl(bool enabled, const QVector<DownloadPriority> &updatedFilePrio = {});

        Session *const m_session;
        lt::torrent_handle m_nativeHandle;
        lt::torrent_status m_nativeStatus;
        TorrentState m_state = TorrentState::Unknown;
        TorrentInfo m_torrentInfo;
        SpeedMonitor m_speedMonitor;

        InfoHash m_hash;

        // m_moveFinishedTriggers is activated only when the following conditions are met:
        // all file rename jobs complete, all file move jobs complete
        QQueue<EventTrigger> m_moveFinishedTriggers;
        int m_renameCount = 0;
        bool m_storageIsMoving = false;

        // Until libtorrent provide an "old_name" field in `file_renamed_alert`
        // we will rely on this workaround to remove empty leftover folders
        QHash<LTFileIndex, QVector<QString>> m_oldPath;

        QHash<QString, TrackerInfo> m_trackerInfos;

        // Persistent data
        QString m_name;
        QString m_savePath;
        QString m_category;
        QSet<QString> m_tags;
        qreal m_ratioLimit;
        int m_seedingTimeLimit;
        bool m_hasSeedStatus;
        bool m_tempPathDisabled;
        bool m_fastresumeDataRejected = false;
        bool m_hasMissingFiles = false;
        bool m_hasRootFolder;
        bool m_needsToSetFirstLastPiecePriority = false;
        bool m_useAutoTMM;

        bool m_unchecked = false;
    };
}
