/*
 * This file is part of the syndication library
 *
 * Copyright (C) 2006 Frank Osterfeld <osterfeld@kde.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "parser.h"
#include "document.h"
#include "model.h"
#include "modelmaker.h"
#include "property.h"
#include "rdfvocab.h"
#include "resource.h"
#include "rssvocab.h"
#include "statement.h"

#include <documentsource.h>

#include <QDomDocument>
#include <QDomNodeList>
#include <QHash>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>

namespace Syndication
{
namespace RDF
{

class Parser::ParserPrivate
{
public:
    QDomDocument addEnumeration(const QDomDocument &doc);
    void map09to10(Model model);
    void addSequenceFor09(Model model);

    QString strInternalNs;
    QString strItemIndex;
};

bool Parser::accept(const DocumentSource &source) const
{
    QDomDocument doc = source.asDomDocument();

    if (doc.isNull()) {
        return false;
    }
    QDomElement root = doc.documentElement();

    if (!root.isElement()) {
        return false;
    }

    return root.namespaceURI() == RDFVocab::self()->namespaceURI();
}

SpecificDocumentPtr Parser::parse(const DocumentSource &source) const
{
    QDomDocument doc = source.asDomDocument();

    if (doc.isNull()) {
        return Syndication::SpecificDocumentPtr(new Document());
    }

    doc = d->addEnumeration(doc);

    ModelMaker maker;
    Model model = maker.createFromXML(doc);

    bool is09 = !model.resourcesWithType(RSS09Vocab::self()->channel()).isEmpty();

    if (is09) {
        d->map09to10(model);
        d->addSequenceFor09(model);
    }

    QList<ResourcePtr> channels = model.resourcesWithType(RSSVocab::self()->channel());

    if (channels.isEmpty()) {
        return Syndication::SpecificDocumentPtr(new Document());
    }

    return DocumentPtr(new Document(*(channels.begin())));
}

QDomDocument Parser::ParserPrivate::addEnumeration(const QDomDocument &docp)
{
    QDomDocument doc(docp);

    const QDomNodeList list = doc.elementsByTagNameNS(RSS09Vocab::self()->namespaceURI(),
                        QStringLiteral("item"));

    for (int i = 0; i < list.size(); ++i) {
        QDomElement item = list.item(i).toElement();
        if (!item.isNull()) {
            QDomElement ie = doc.createElementNS(strInternalNs, strItemIndex);
            item.appendChild(ie);
            ie.appendChild(doc.createTextNode(QString::number(i)));

        }
    }

    return doc;
}

void Parser::ParserPrivate::map09to10(Model model)
{
    QHash<QString, PropertyPtr> hash;

    hash.insert(RSS09Vocab::self()->title()->uri(), RSSVocab::self()->title());
    hash.insert(RSS09Vocab::self()->description()->uri(), RSSVocab::self()->description());
    hash.insert(RSS09Vocab::self()->link()->uri(), RSSVocab::self()->link());
    hash.insert(RSS09Vocab::self()->name()->uri(), RSSVocab::self()->name());
    hash.insert(RSS09Vocab::self()->url()->uri(), RSSVocab::self()->url());
    hash.insert(RSS09Vocab::self()->image()->uri(), RSSVocab::self()->image());
    hash.insert(RSS09Vocab::self()->textinput()->uri(), RSSVocab::self()->textinput());

    QStringList uris09 = RSS09Vocab::self()->properties();

    // map statement predicates to RSS 1.0

    QList<StatementPtr> statements = model.statements();
    QList<StatementPtr>::ConstIterator it = statements.constBegin();
    QList<StatementPtr>::ConstIterator end = statements.constEnd();

    for (; it != end; ++it) {
        StatementPtr stmt = *it;

        QString predUri = stmt->predicate()->uri();
        if (uris09.contains(predUri)) {
            model.addStatement(stmt->subject(), hash[predUri], stmt->object());
        }
    }
    // map channel type
    QList<ResourcePtr> channels = model.resourcesWithType(RSS09Vocab::self()->channel());

    ResourcePtr channel;

    if (!channels.isEmpty()) {
        channel = *(channels.begin());

        model.removeStatement(channel, RDFVocab::self()->type(), RSS09Vocab::self()->channel());
        model.addStatement(channel, RDFVocab::self()->type(), RSSVocab::self()->channel());
    }
}

void Parser::ParserPrivate::addSequenceFor09(Model model)
{
    //RDF 0.9 doesn't contain an item sequence, and the items don't have rdf:about, so add both

    const QList<ResourcePtr> items = model.resourcesWithType(RSS09Vocab::self()->item());

    if (items.isEmpty()) {
        return;
    }

    const QList<ResourcePtr> channels = model.resourcesWithType(RSSVocab::self()->channel());

    if (channels.isEmpty()) {
        return;
    }

    PropertyPtr itemIndex = model.createProperty(strInternalNs + strItemIndex);

    // use QMap here, not QHash. as we need the sorting functionality
    QMap<uint, ResourcePtr> sorted;

    for (const ResourcePtr &i : items) {
        QString numstr = i->property(itemIndex)->asString();
        bool ok = false;
        uint num = numstr.toUInt(&ok);
        if (ok) {
            sorted[num] = i;
        }
    }

    SequencePtr seq = model.createSequence();
    model.addStatement(channels.first(), RSSVocab::self()->items(), seq);

    for (const ResourcePtr &i : qAsConst(sorted)) {
        seq->append(i);
        // add rdf:about (type)
        model.addStatement(i, RDFVocab::self()->type(), RSSVocab::self()->item());

        //add to items sequence
        model.addStatement(seq, RDFVocab::self()->li(), i);
    }
}

Parser::Parser() : d(new ParserPrivate)
{
    d->strInternalNs = QStringLiteral("http://akregator.sf.net/libsyndication/internal#");
    d->strItemIndex = QStringLiteral("itemIndex");
}

Parser::~Parser()
{
    delete d;
}

Parser::Parser(const Parser &other) : AbstractParser(other), d(nullptr) {}
Parser &Parser::operator=(const Parser & /*other*/)
{
    return *this;
}

QString Parser::format() const
{
    return QStringLiteral("rdf");
}

} // namespace RDF
} // namespace Syndication
