/**************************************************************************
 *   Copyright (C) 2009-2011 Matthias Fuchs <mat69@gmx.net>                *
 *                                                                         *
 *   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 .        *
 ***************************************************************************/

#ifndef VERIFIER_H
#define VERIFIER_H

#include <QUrl>
#include <kio/global.h>

#include <QHash>
#include <QModelIndex>
#include <QStringList>

#include "kget_export.h"

class QDomElement;
class VerificationModel;
class VerifierPrivate;
typedef QPair<QString, QString> Checksum;

class KGET_EXPORT PartialChecksums
{
public:
    PartialChecksums()
        : m_length(0)
    {
    }

    PartialChecksums(KIO::filesize_t len, const QStringList &sums)
        : m_length(len)
        , m_checksums(sums)
    {
    }

    bool isValid() const
    {
        return (length() && m_checksums.count());
    }

    KIO::filesize_t length() const
    {
        return m_length;
    }
    void setLength(KIO::filesize_t length)
    {
        m_length = length;
    }

    QStringList checksums() const
    {
        return m_checksums;
    }
    void setChecksums(const QStringList &checksums)
    {
        m_checksums = checksums;
    }

private:
    KIO::filesize_t m_length;
    QStringList m_checksums;
};

Q_DECLARE_METATYPE(PartialChecksums)

class KGET_EXPORT Verifier : public QObject
{
    Q_OBJECT

public:
    explicit Verifier(const QUrl &dest, QObject *parent = nullptr);
    ~Verifier() override;

    enum VerificationStatus {
        NoResult, // either not tried, or not enough information
        NotVerified,
        Verified
    };

    enum ChecksumStrength {
        Weak,
        Strong,
        Strongest
    };

    /**
     * @returns the object path that will be shown in the DBUS interface
     */
    QString dBusObjectPath() const;

    QUrl destination() const;
    void setDestination(const QUrl &destination); // TODO handle the case when m_thread is working, while the file gets moved

    VerificationStatus status() const;

    /**
     * Returns the supported verification types
     * @return the supported verification types (e.g. MD5, SHA1 ...)
     */
    static QStringList supportedVerficationTypes();

    /**
     * Returns the diggest length of type
     * @param type the checksum type for which to get the diggest length
     * @return the length the diggest should have
     */
    static int diggestLength(const QString &type);

    /**
     * Tries to check if the checksum is a checksum and if it is supported
     * it compares the diggestLength and checks if there are only alphanumerics in checksum
     * @param type of the checksum
     * @param checksum the checksum you want to check
     */
    static bool isChecksum(const QString &type, const QString &checksum);

    /**
     * Cleans the checksum type, that it should match the official name, i.e. upper case
     * e.g. SHA-1 instead of sha1
     */
    static QString cleanChecksumType(const QString &type);

    /**
     * Creates the checksum type of the file @p dest
     * @param dest the destination
     * @param type the type of the checksum
     * @param abortPtr makes it possible to abort the calculation of the checksum from another thread
     */
    static QString checksum(const QUrl &dest, const QString &type, bool *abortPtr);

    /**
     * Create partial checksums of type for file @p dest
     * @param dest the destination
     * @param type the type of the checksum
     * @param length the file length
     * @param abortPtr makes it possible to abort the calculation of the checksums from another thread
     * @note the length of the partial checksum (if not defined = 0) is not less than 512 kb
     * and there won't be more partial checksums than 101
     */
    static PartialChecksums partialChecksums(const QUrl &dest, const QString &type, KIO::filesize_t length = 0, bool *abortPtr = nullptr);

    /**
     * @note only call verify() when this function returns true
     * @return true if the downloaded file exists and a supported checksum is set
     */
    bool isVerifyable() const;

    /**
     * Convenience function if only a row of the model should be checked
     * @note only call verify() when this function returns true
     * @param index the row in the model of the checksum
     * @return true if the downloaded file exists and a supported checksum is set
     */
    bool isVerifyable(const QModelIndex &index) const;

    /**
     * Call this method if you want to verify() in its own thread, then signals with
     * the result are emitted
     * @param index row of the model should be checked, if not defined the a checkum defined by
     * Verifier::ChecksumStrength will be used
     */
    void verify(const QModelIndex &index = QModelIndex());

    /**
     * Call this method after calling verify() with a negative result, it will
     * Q_EMIT a list of the broken pieces, if PartialChecksums were defined,
     * otherwise and in case of any error an empty list will be emitted
     */
    void brokenPieces() const;

    /**
     * Add a checksum that is later used in the verification process
     * @note only one checksum per type can be added (one MD5, one SHA1 etc.),
     * the newer overwrites the older and a checksum can only be added if it is
     * supported by the verifier
     * @param type the type of the checksum
     * @param checksum the checksum
     * @param verified if the file has been verified using this checksum
     * @note uses VerificationModel internally
     * @see VerificationModel
     */
    void addChecksum(const QString &type, const QString &checksum, int verified = 0);

    /**
     * Add multiple checksums that will later be used in the verification process
     * @note only one checksum per type can be added (one MD5, one SHA1 etc.),
     * the newer overwrites the older and a checksum can only be added if it is
     * supported by the verifier
     * @param checksums <type, checksum>
     * @note uses VerificationModel internally
     * @see VerificationModel
     */
    void addChecksums(const QMultiHash<QString, QString> &checksums);

    /**
     * Add partial checksums that can be used as repairinformation
     * @note only one checksum per type can be added (one MD5, one SHA1 etc.),
     * the newer overwrites the older and a checksum can only be added if it is
     * supported by the verifier
     * @param type the type of the checksums
     * @param length the length of each piece
     * @param checksums the checksums, first entry is piece number 0
     */
    void addPartialChecksums(const QString &type, KIO::filesize_t length, const QStringList &checksums);

    /**
     * Returns the length of the "best" partialChecksums
     */
    KIO::filesize_t partialChunkLength() const;

    /**
     * Returns a checksum and a type
     * @param strength the strength the checksum-type should have;
     * weak is md5 > md4 (md5 preferred), strong is sha1 > ripmed160 >
     * sha256 > sha384 > sha512 (sha1 preferred), strongest is sha512 >
     * sha384 > sha256 .... < (sha512 preferred)
     * If the category does not match then any checksum is taken
     */
    Checksum availableChecksum(ChecksumStrength strength) const;

    /**
     * Returns all set checksums
     */
    QList<Checksum> availableChecksums() const;

    /**
     * Returns a PartialChecksum and a type
     * @param strength the strength the checksum-type should have;
     * weak is md5 > md4 > ... (md5 preferred), strong is sha1 > ripmed160 >
     * sha256 > sha384 > sha512 > ... (sha1 preferred), strongest is sha512 >
     * sha384 > sha256 ... (sha512 preferred)
     * If the category does not match then any checksum is taken
     */
    QPair<QString, PartialChecksums *> availablePartialChecksum(Verifier::ChecksumStrength strength) const;

    /**
     * @return the model that stores the hash-types and checksums
     */
    VerificationModel *model();

    void save(const QDomElement &element);
    void load(const QDomElement &e);

Q_SIGNALS:
    /**
     * Emitted when the verification of a file finishes
     */
    void verified(bool verified);

    /**
     * Emitted when brokenPiecesThreaded finishes, the list can be empty, while length will be always set
     */
    void brokenPieces(const QList<KIO::fileoffset_t> &offsets, KIO::filesize_t length);

private Q_SLOTS:
    void changeStatus(const QString &type, bool verified);

private:
    VerifierPrivate *const d;

    friend class VerifierPrivate;
};

#endif // VERIFIER_H
