// SPDX-FileCopyrightText: 2009 Jens-Michael Hoffmann <jmho@c-xx.com>
//
// SPDX-License-Identifier: LGPL-2.1-or-later

#ifndef MARBLE_DOWNLOADQUEUESET_H
#define MARBLE_DOWNLOADQUEUESET_H

#include <QList>
#include <QObject>
#include <QQueue>
#include <QSet>
#include <QStack>

#include "DownloadPolicy.h"

class QUrl;

namespace Marble
{

class HttpJob;

/**
   Life of a HttpJob
   =================
   - Job is added to the QueueSet (by calling addJob() )
     the HttpJob is put into the m_jobQueue where it waits for "activation"
     signal jobAdded is emitted
   - Job is activated
     Job is moved from m_jobQueue to m_activeJobs and signals of the job
     are connected to slots (local or HttpDownloadManager)
     Job is executed by calling the jobs execute() method

   now there are different possibilities:
   1) Job emits jobDone (some error occurred, or canceled (kio))
      Job is disconnected
      signal jobRemoved is emitted
      Job is either moved from m_activeJobs to m_retryQueue
        or destroyed and blacklisted

   2) Job emits redirected
      Job is removed from m_activeJobs, disconnected and destroyed
      signal jobRemoved is emitted
      (HttpDownloadManager creates new Job with new source url)

   3) Job emits dataReceived
      Job is removed from m_activeJobs, disconnected and destroyed
      signal jobRemoved is emitted

   so we can conclude following rules:
   - Job is only connected to signals when in "active" state


   questions:
   - update of initiatorId needed?
     "We update the initiatorId as the previous initiator
      likely doesn't exist anymore"
   - blacklist or black list?

 */

class DownloadQueueSet : public QObject
{
    Q_OBJECT

public:
    explicit DownloadQueueSet(QObject *const parent = nullptr);
    explicit DownloadQueueSet(const DownloadPolicy &policy, QObject *const parent = nullptr);
    ~DownloadQueueSet() override;

    DownloadPolicy downloadPolicy() const;
    void setDownloadPolicy(const DownloadPolicy &);

    bool canAcceptJob(const QUrl &sourceUrl, const QString &destinationFileName) const;
    void addJob(HttpJob *const job);

    void activateJobs();
    void retryJobs();
    void purgeJobs();

Q_SIGNALS:
    void jobAdded();
    void jobRemoved();
    void jobRetry();
    void jobFinished(const QByteArray &data, const QString &destinationFileName, const QString &id);
    void jobRedirected(const QUrl &newSourceUrl, const QString &destinationFileName, const QString &id, DownloadUsage);
    void progressChanged(int active, int queued);

private Q_SLOTS:
    void finishJob(HttpJob *job, const QByteArray &data);
    void redirectJob(HttpJob *job, const QUrl &newSourceUrl);
    void retryOrBlacklistJob(HttpJob *job, const int errorCode);

private:
    void activateJob(HttpJob *const job);
    void deactivateJob(HttpJob *const job);
    bool jobIsActive(const QString &destinationFileName) const;
    bool jobIsQueued(const QString &destinationFileName) const;
    bool jobIsWaitingForRetry(const QString &destinationFileName) const;
    bool jobIsBlackListed(const QUrl &sourceUrl) const;

    DownloadPolicy m_downloadPolicy;

    /** This is the first stage a job enters, from this queue it will get
     *  into the activatedJobs container.
     */
    class JobStack
    {
    public:
        bool contains(const QString &destinationFileName) const;
        int count() const;
        bool isEmpty() const;
        HttpJob *pop();
        void push(HttpJob *const);

    private:
        QStack<HttpJob *> m_jobs;
        QSet<QString> m_jobsContent;
    };
    JobStack m_jobs;

    /// Contains the jobs which are currently being downloaded.
    QList<HttpJob *> m_activeJobs;

    /** Contains jobs which failed to download and which are scheduled for
     *  retry according to retry settings.
     */
    QQueue<HttpJob *> m_retryQueue;

    /// Contains the blacklisted source urls
    QSet<QString> m_jobBlackList;
};

}

#endif
