/*
    Copyright (c) 2014 Daniel Vrátil <dvratil@redhat.com>

    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 "searchquery.h"

#include <QtCore/QVariant>

#include <KDebug>

#include <qjson/parser.h>
#include <qjson/serializer.h>

using namespace Akonadi;

class SearchTerm::Private : public QSharedData
{
public:
    Private()
        : QSharedData()
        , condition(SearchTerm::CondEqual)
        , relation(SearchTerm::RelAnd)
        , isNegated(false)
    {
    }

    Private(const Private &other)
        : QSharedData(other)
        , key(other.key)
        , value(other.value)
        , condition(other.condition)
        , relation(other.relation)
        , terms(other.terms)
        , isNegated(other.isNegated)
    {
    }

    bool operator==(const Private &other) const
    {
        return relation == other.relation
               && isNegated == other.isNegated
               && terms == other.terms
               && key == other.key
               && value == other.value
               && condition == other.condition;
    }

    QString key;
    QVariant value;
    Condition condition;
    Relation relation;
    QList<SearchTerm> terms;
    bool isNegated;
};

class SearchQuery::Private : public QSharedData
{
public:
    Private()
        : QSharedData()
        , limit(-1)
    {
    }

    Private(const Private &other)
        : QSharedData(other)
        , rootTerm(other.rootTerm)
        , limit(other.limit)
    {
    }

    bool operator==(const Private &other) const
    {
        return rootTerm == other.rootTerm && limit == other.limit;
    }

    static QVariantMap termToJSON(const SearchTerm &term)
    {
        const QList<SearchTerm> &subTerms = term.subTerms();
        QVariantMap termJSON;
        termJSON.insert(QLatin1String("negated"), term.isNegated());
        if (subTerms.isEmpty()) {
            termJSON.insert(QLatin1String("key"), term.key());
            termJSON.insert(QLatin1String("value"), term.value());
            termJSON.insert(QLatin1String("cond"), static_cast<int>(term.condition()));
        } else {
            termJSON.insert(QLatin1String("rel"), static_cast<int>(term.relation()));
            QVariantList subTermsJSON;
            Q_FOREACH (const SearchTerm &term, subTerms) {
                subTermsJSON.append(termToJSON(term));
            }
            termJSON.insert(QLatin1String("subTerms"), subTermsJSON);
        }

        return termJSON;
    }

    static SearchTerm JSONToTerm(const QVariantMap &json)
    {
        if (json.contains(QLatin1String("key"))) {
            SearchTerm term(json[QLatin1String("key")].toString(),
                            json[QLatin1String("value")],
                            static_cast<SearchTerm::Condition>(json[QLatin1String("cond")].toInt()));
            term.setIsNegated(json[QLatin1String("negated")].toBool());
            return term;
        } else if (json.contains(QLatin1String("rel"))) {
            SearchTerm term(static_cast<SearchTerm::Relation>(json[QLatin1String("rel")].toInt()));
            term.setIsNegated(json[QLatin1String("negated")].toBool());
            const QVariantList subTermsJSON = json[QLatin1String("subTerms")].toList();
            Q_FOREACH (const QVariant &subTermJSON, subTermsJSON) {
                term.addSubTerm(JSONToTerm(subTermJSON.toMap()));
            }
            return term;
        } else {
            kWarning() << "Invalid JSON for term: " << json;
            return SearchTerm();
        }
    }

    SearchTerm rootTerm;
    int limit;
};

SearchTerm::SearchTerm(SearchTerm::Relation relation)
    : d(new Private)
{
    d->relation = relation;
}

SearchTerm::SearchTerm(const QString &key, const QVariant &value, SearchTerm::Condition condition)
    : d(new Private)
{
    d->relation = RelAnd;
    d->key = key;
    d->value = value;
    d->condition = condition;
}

SearchTerm::SearchTerm(const SearchTerm &other)
    : d(other.d)
{
}

SearchTerm::~SearchTerm()
{
}

SearchTerm &SearchTerm::operator=(const SearchTerm &other)
{
    d = other.d;
    return *this;
}

bool SearchTerm::operator==(const SearchTerm &other) const
{
    return *d == *other.d;
}

bool SearchTerm::isNull() const
{
    return d->key.isEmpty() && d->value.isNull() && d->terms.isEmpty();
}

QString SearchTerm::key() const
{
    return d->key;
}

QVariant SearchTerm::value() const
{
    return d->value;
}

SearchTerm::Condition SearchTerm::condition() const
{
    return d->condition;
}

void SearchTerm::setIsNegated(bool negated)
{
    d->isNegated = negated;
}

bool SearchTerm::isNegated() const
{
    return d->isNegated;
}

void SearchTerm::addSubTerm(const SearchTerm &term)
{
    d->terms << term;
}

QList< SearchTerm > SearchTerm::subTerms() const
{
    return d->terms;
}

SearchTerm::Relation SearchTerm::relation() const
{
    return d->relation;
}

SearchQuery::SearchQuery(SearchTerm::Relation rel)
    : d(new Private)
{
    d->rootTerm = SearchTerm(rel);
}

SearchQuery::SearchQuery(const SearchQuery &other)
    : d(other.d)
{
}

SearchQuery::~SearchQuery()
{
}

SearchQuery &SearchQuery::operator=(const SearchQuery &other)
{
    d = other.d;
    return *this;
}

bool SearchQuery::operator==(const SearchQuery &other) const
{
    return *d == *other.d;
}

bool SearchQuery::isNull() const
{
    return d->rootTerm.isNull();
}

SearchTerm SearchQuery::term() const
{
    return d->rootTerm;
}

void SearchQuery::addTerm(const QString &key, const QVariant &value, SearchTerm::Condition condition)
{
    addTerm(SearchTerm(key, value, condition));
}

void SearchQuery::addTerm(const SearchTerm &term)
{
    d->rootTerm.addSubTerm(term);
}

void SearchQuery::setTerm(const SearchTerm &term)
{
    d->rootTerm = term;
}

void SearchQuery::setLimit(int limit)
{
    d->limit = limit;
}

int SearchQuery::limit() const
{
    return d->limit;
}

QByteArray SearchQuery::toJSON() const
{
    QVariantMap root = Private::termToJSON(d->rootTerm);
    root.insert(QLatin1String("limit"), d->limit);

    QJson::Serializer serializer;
    return serializer.serialize(root);
}

SearchQuery SearchQuery::fromJSON(const QByteArray &jsonData)
{
    QJson::Parser parser;
    bool ok = false;
    const QVariant json = parser.parse(jsonData, &ok);
    if (!ok || json.isNull()) {
        return SearchQuery();
    }

    const QVariantMap map = json.toMap();
    SearchQuery query;
    query.d->rootTerm = Private::JSONToTerm(map);
    if (map.contains(QLatin1String("limit"))) {
        query.d->limit = map.value(QLatin1String("limit")).toInt();
    }
    return query;
}

QMap<EmailSearchTerm::EmailSearchField, QString> initializeMapping()
{
    QMap<EmailSearchTerm::EmailSearchField, QString> mapping;
    mapping.insert(EmailSearchTerm::Body, QLatin1String("body"));
    mapping.insert(EmailSearchTerm::Headers, QLatin1String("headers"));
    mapping.insert(EmailSearchTerm::Subject, QLatin1String("subject"));
    mapping.insert(EmailSearchTerm::Message, QLatin1String("message"));
    mapping.insert(EmailSearchTerm::HeaderFrom, QLatin1String("from"));
    mapping.insert(EmailSearchTerm::HeaderTo, QLatin1String("to"));
    mapping.insert(EmailSearchTerm::HeaderCC, QLatin1String("cc"));
    mapping.insert(EmailSearchTerm::HeaderBCC, QLatin1String("bcc"));
    mapping.insert(EmailSearchTerm::HeaderReplyTo, QLatin1String("replyto"));
    mapping.insert(EmailSearchTerm::HeaderOrganization, QLatin1String("organization"));
    mapping.insert(EmailSearchTerm::HeaderListId, QLatin1String("listid"));
    mapping.insert(EmailSearchTerm::HeaderResentFrom, QLatin1String("resentfrom"));
    mapping.insert(EmailSearchTerm::HeaderXLoop, QLatin1String("xloop"));
    mapping.insert(EmailSearchTerm::HeaderXMailingList, QLatin1String("xmailinglist"));
    mapping.insert(EmailSearchTerm::HeaderXSpamFlag, QLatin1String("xspamflag"));
    mapping.insert(EmailSearchTerm::HeaderDate, QLatin1String("date"));
    mapping.insert(EmailSearchTerm::HeaderOnlyDate, QLatin1String("onlydate"));
    mapping.insert(EmailSearchTerm::MessageStatus, QLatin1String("messagestatus"));
    mapping.insert(EmailSearchTerm::MessageTag, QLatin1String("messagetag"));
    mapping.insert(EmailSearchTerm::ByteSize, QLatin1String("size"));
    mapping.insert(EmailSearchTerm::Attachment, QLatin1String("attachment"));
    return mapping;
}

static QMap<EmailSearchTerm::EmailSearchField, QString> emailSearchFieldMapping = initializeMapping();

EmailSearchTerm::EmailSearchTerm(EmailSearchTerm::EmailSearchField field, const QVariant &value, SearchTerm::Condition condition)
    : SearchTerm(toKey(field), value, condition)
{

}

QString EmailSearchTerm::toKey(EmailSearchTerm::EmailSearchField field)
{
    return emailSearchFieldMapping.value(field);
}

EmailSearchTerm::EmailSearchField EmailSearchTerm::fromKey(const QString &key)
{
    return emailSearchFieldMapping.key(key);
}

QMap<ContactSearchTerm::ContactSearchField, QString> initializeContactMapping()
{
    QMap<ContactSearchTerm::ContactSearchField, QString> mapping;
    mapping.insert(ContactSearchTerm::Name, QLatin1String("name"));
    mapping.insert(ContactSearchTerm::Nickname, QLatin1String("nickname"));
    mapping.insert(ContactSearchTerm::Email, QLatin1String("email"));
    mapping.insert(ContactSearchTerm::Uid, QLatin1String("uid"));
    mapping.insert(ContactSearchTerm::All, QLatin1String("all"));
    return mapping;
}

static QMap<ContactSearchTerm::ContactSearchField, QString> contactSearchFieldMapping = initializeContactMapping();

ContactSearchTerm::ContactSearchTerm(ContactSearchTerm::ContactSearchField field, const QVariant &value, SearchTerm::Condition condition)
    : SearchTerm(toKey(field), value, condition)
{

}

QString ContactSearchTerm::toKey(ContactSearchTerm::ContactSearchField field)
{
    return contactSearchFieldMapping.value(field);
}

ContactSearchTerm::ContactSearchField ContactSearchTerm::fromKey(const QString &key)
{
    return contactSearchFieldMapping.key(key);
}
