/***************************************************************************
 *   Copyright (C) 2009 Matthias Fuchs <mat69@gmx.net>                     *
 *   Copyright (C) 2012 Aish Raj Dahal <dahalaishraj@gmail.com>            *
 *                                                                         *
 *   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 Metalinker_H
#define Metalinker_H

#include <KIO/Job>
#include <QDate>
#include <QDomElement>
#include <QEventLoop>
#include <QObject>
#include <QString>
#include <QUrl>

/**
 * The following classes try to resemble the structure of a Metalink-document, they partially support
 * the Metalink specification version 3.0 2nd ed and Draft 09
 * It is possible to load and save metalinks and to edit them inbetween, you could also create a metalink
 * from scratch
 */

namespace KGetMetalink
{

class DateConstruct
{
public:
    DateConstruct()
        : negativeOffset(false)
    {
    }

    void setData(const QDateTime &dateTime, const QTime &timeZoneOffset = QTime(), bool negativeOffset = false);
    void setData(const QString &dateConstruct);

    void clear();

    bool isNull() const;
    bool isValid() const;

    QString toString() const;

    QDateTime dateTime;
    QTime timeZoneOffset;
    bool negativeOffset;
};

/**
 * This class contains a url and the name, it can be used to e.g. describe a publisher
 */
class UrlText
{
public:
    UrlText()
    {
    }

    bool isEmpty() const
    {
        return name.isEmpty() && url.isEmpty();
    }

    void clear();

    QString name;
    QUrl url;
};

/**
 * Files, File and Metadata contain this
 * Metadata not as member and only for compatibility
 */
class CommonData
{
public:
    CommonData()
    {
    }

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

    void clear();

    QString identity;
    QString version;
    QString description;
    QStringList oses;
    QUrl logo;
    QStringList languages;
    UrlText publisher;
    QString copyright;
};

class Metaurl
{
public:
    Metaurl()
        : priority(0)
    {
    }

    /**
     * "smaller" urls are less important than larger, larger urls should be preferred
     */
    bool operator<(const Metaurl &other) const;

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

    bool isValid();

    void clear();

    QString type;

    /**
     * the priority of the urls, 1 is highest priority, 999999 lowest
     * default is 0 as in not set and thus is ranked even behind 999999
     */
    uint priority;

    /**
     * Optional the name of a file that should be get of that metaurl
     */
    QString name;

    QUrl url;
};

class Url
{
public:
    Url()
        : priority(0)
    {
    }

    /**
     * "smaller" urls are less important than larger, larger urls should be preferred
     */
    bool operator<(const Url &other) const;

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

    bool isValid();

    void clear();

    /**
     * the priority of the urls, 1 is highest priority, 999999 lowest
     * default is 0 as in not set and thus is ranked even behind 999999
     */
    uint priority;

    /**
     * the location of the server eg. "uk"
     */
    QString location;

    QUrl url;
};

class Resources
{
public:
    Resources()
    {
    }

    bool isValid() const
    {
        return !urls.isEmpty() || !metaurls.isEmpty();
    }

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

    void clear();

    QList<Url> urls;
    QList<Metaurl> metaurls;
};

class Pieces
{
public:
    Pieces()
        : length(0)
    {
    }

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

    void clear();

    QString type;
    KIO::filesize_t length;
    QStringList hashes;
};

class Verification
{
public:
    Verification()
    {
    }

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

    void clear();

    QHash<QString, QString> hashes;
    QList<Pieces> pieces;
    QHash<QString, QString> signatures;
};

class File
{
public:
    File()
        : size(0)
    {
    }

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

    void clear();

    bool isValid() const;

    /**
     * Controls if the name attribute is valid, i.e. it is not empty and
     * does not contain any directory traversal directives or information,
     * as described in the Metalink 4.0 specification 4.1.2.1.
     */
    bool isValidNameAttribute() const;

    QString name;
    Verification verification;
    KIO::filesize_t size;
    CommonData data;
    Resources resources;
};

class Files
{
public:
    Files()
    {
    }

    bool isValid() const;

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

    void clear();

    QList<File> files;
};

class Metalink
{
public:
    Metalink()
        : dynamic(false)
    {
    }

    /**
     * checks if the minimum requirements of a metalink are met
     * @return true if the minimum requirements are met
     */
    bool isValid() const;

    void load(const QDomElement &e);

    /**
     * Save the metalink
     * @return the QDomDocument containing the metalink
     */
    QDomDocument save() const;

    void clear();

    bool dynamic;
    QString xmlns; // the xmlns value is ignored when saving, instead the data format described in the specification is always used
    DateConstruct published; // when the metalink was published
    QUrl origin;
    QString generator;
    DateConstruct updated; // when the metalink was updated
    Files files;

    static const QString KGET_DESCRIPTION;
    static const uint MAX_URL_PRIORITY; // maximum priority a Metalink 4.0 Url or Metaurl can have, not to be mixed up with the highest priority
};

/**
 * @class Metalink_v3
 * Metalink version 3.0 2nd ed
 * Used only for loading and saving, uses itself Metalink internally
 */
class Metalink_v3
{
public:
    Metalink_v3();

    Metalink metalink();
    void setMetalink(const Metalink &metalink);

    void load(const QDomElement &e);

    /**
     * Save the metalink
     * @return the QDomDocument containing the metalink
     */
    QDomDocument save() const;

private:
    void parseFiles(const QDomElement &e);
    Resources parseResources(const QDomElement &e);
    DateConstruct parseDateConstruct(const QString &data);
    CommonData parseCommonData(const QDomElement &e);

    /**
     * Inherits CommonData, the inheritor inherits every settings
     * from the ancestor it has not set itself
     */
    void inheritCommonData(const CommonData &ancestor, CommonData *inheritor);

    void saveFiles(QDomElement &e) const;
    void saveResources(const Resources &resources, QDomElement &e) const;
    void saveVerification(const Verification &verification, QDomElement &e) const;
    void saveCommonData(const CommonData &data, QDomElement &e) const;
    QString dateConstructToString(const DateConstruct &date) const;

private:
    Metalink m_metalink;
    static const uint MAX_PREFERENCE;
};

/**
 * This class can handle the loading and saving of metalinks on the filesystem
 */
class HandleMetalink
{
public:
    /**
     * Loads destination into metalink
     * @param destination the place of the metalink in the filesystem
     * @param metalink the instance of Metalink where the metalink will be stored
     * @return return true if it worked
     */
    static bool load(const QUrl &destination, Metalink *metalink);

    /**
     * Loads data into metalink
     * @param data the contents of a metalink
     * @param metalink the instance of Metalink where the metalink will be stored
     * @return return true if it worked
     */
    static bool load(const QByteArray &data, Metalink *metalink);

    /**
     * Saves metalink to destination
     * @param destination the place where the metlink will be saved, the ending defines
     * what version should be used: *.meta4 --> Metalink 4.0; *.metalink --> Metalink 3.0
     * @param metalink the instance of metalink that will be written to the filesystem
     * @return return true if it worked
     */
    static bool save(const QUrl &destination, Metalink *metalink);
};

class MetalinkHttpParser : public QObject
{
    Q_OBJECT
public:
    MetalinkHttpParser(const QUrl &Url)
        : m_Url(Url)
        , m_MetalinkHSatus(false)
        , m_EtagValue(QString(""))
    {
        checkMetalinkHttp();
    }

    ~MetalinkHttpParser() override;

    /**
     * @return true if m_Url is a metalink/http supported URL.
     */

    bool isMetalinkHttp();

    /**
     * @return the Url m_Url which is being tested for metalink
     */

    QUrl getUrl();
    QMultiMap<QString, QString> *getHeaderInfo();

    /**
     * @return Returns the ETag if present in the HTTP headers
     */

    QString *getEtag();

private Q_SLOTS:
    void slotHeaderResult(KJob *kjob);
    void checkMetalinkHttp();
    void detectMime(KIO::Job *job, const QString &type);
    void slotRedirection(KIO::Job *, const QUrl &);

private:
    QUrl m_Url;
    QUrl m_redirectionUrl;
    bool m_MetalinkHSatus;
    QEventLoop m_loop;
    QMultiMap<QString, QString> m_headerInfo;
    QString m_EtagValue;

    /**
     * Parsees the Metalink values from QString to the Map
     * @param Value of the QString ie raw HTTP headers
     */
    void parseHeaders(const QString &);

    /**
     * Sets the status of m_MetalinkHStatus to true if the URL is a Metalink
     */
    void setMetalinkHSatus();
};

class HttpLinkHeader : public Metaurl
{
public:
    HttpLinkHeader(const QString &headerLine);

    QString reltype;
    bool pref;
    int depth;
    QString geo;

    /**
     * Loads information from a header value into metalink header structure.
     * @param line Value of the "link" HTTP header response.
     */
    void parseHeaderLine(const QString &);

    bool operator<(const HttpLinkHeader &) const;
};

}

#endif // Metalinker_H
