/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtXmlPatterns module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtDebug>

#include "private/qxmlutils_p.h"
#include "qxpathhelper_p.h"

#include "qnamepool_p.h"

QT_BEGIN_NAMESPACE

using namespace QPatternist;

NamePool::NamePool()
{
    m_localNameMapping  .reserve(DefaultLocalNameCapacity   + StandardLocalNameCount);
    m_localNames        .reserve(DefaultLocalNameCapacity   + StandardLocalNameCount);
    m_namespaceMapping  .reserve(DefaultURICapacity         + StandardNamespaceCount);
    m_namespaces        .reserve(DefaultURICapacity         + StandardNamespaceCount);
    m_prefixes          .reserve(DefaultPrefixCapacity      + StandardPrefixCount);
    m_prefixMapping     .reserve(DefaultPrefixCapacity      + StandardPrefixCount);

    /* Namespaces. */
    {
        unlockedAllocateNamespace(QString());
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/2005/xpath-functions"));
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/2005/xquery-local-functions"));
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/XML/1998/namespace"));
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/2000/xmlns/"));
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/2001/XMLSchema"));
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/2001/XMLSchema-instance"));
        unlockedAllocateNamespace(QLatin1String("http://www.w3.org/1999/XSL/Transform"));

        /* For UndeclarePrefix, StopNamespaceInheritance and InternalXSLT. We use two
         * arbitrary strings that aren't used. For instance, if we just used an
         * empty QString, unlockedAllocateNamespace() would assign it
         * StandardNamespaces::empty. However, it's important that the string
         * is an invalid namespace, since otherwise user strings would get
         * assigned the same IDs. */
        unlockedAllocateNamespace(QLatin1String("  |  1  "));
        unlockedAllocateNamespace(QLatin1String("  |  2  "));
        unlockedAllocateNamespace(QLatin1String("  |  InternalXSLT"));

        Q_ASSERT_X(m_namespaces.count() == StandardNamespaceCount, Q_FUNC_INFO,
                   qPrintable(QString::fromLatin1("Expected is %1, actual is %2.").arg(StandardNamespaceCount).arg(m_namespaces.count())));
    }

    /* Prefixes. */
    {
        unlockedAllocatePrefix(QString());
        unlockedAllocatePrefix(QLatin1String("fn"));
        unlockedAllocatePrefix(QLatin1String("local"));
        unlockedAllocatePrefix(QLatin1String("xml"));
        unlockedAllocatePrefix(QLatin1String("xmlns"));
        unlockedAllocatePrefix(QLatin1String("xs"));
        unlockedAllocatePrefix(QLatin1String("xsi"));
        unlockedAllocatePrefix(QLatin1String("ns0"));
        unlockedAllocatePrefix(QLatin1String("|||")); /* Invalid NCName, for StopNamespaceInheritance. */

        Q_ASSERT_X(m_prefixes.count() == StandardPrefixCount, Q_FUNC_INFO,
                   qPrintable(QString::fromLatin1("Expected is %1, actual is %2.").arg(StandardPrefixCount).arg(m_prefixes.count())));
    }

    /* Local names. */
    {
        /* Same order as the enum in StandardLocalNames. That is, alphabetically. */
        unlockedAllocateLocalName(QLatin1String("abs"));
        unlockedAllocateLocalName(QLatin1String("adjust-dateTime-to-timezone"));
        unlockedAllocateLocalName(QLatin1String("adjust-date-to-timezone"));
        unlockedAllocateLocalName(QLatin1String("adjust-time-to-timezone"));
        unlockedAllocateLocalName(QLatin1String("all"));
        unlockedAllocateLocalName(QLatin1String("arity"));
        unlockedAllocateLocalName(QLatin1String("avg"));
        unlockedAllocateLocalName(QLatin1String("base"));
        unlockedAllocateLocalName(QLatin1String("base-uri"));
        unlockedAllocateLocalName(QLatin1String("boolean"));
        unlockedAllocateLocalName(QLatin1String("ceiling"));
        unlockedAllocateLocalName(QLatin1String("codepoint-equal"));
        unlockedAllocateLocalName(QLatin1String("codepoints-to-string"));
        unlockedAllocateLocalName(QLatin1String("collection"));
        unlockedAllocateLocalName(QLatin1String("compare"));
        unlockedAllocateLocalName(QLatin1String("concat"));
        unlockedAllocateLocalName(QLatin1String("contains"));
        unlockedAllocateLocalName(QLatin1String("count"));
        unlockedAllocateLocalName(QLatin1String("current"));
        unlockedAllocateLocalName(QLatin1String("current-date"));
        unlockedAllocateLocalName(QLatin1String("current-dateTime"));
        unlockedAllocateLocalName(QLatin1String("current-time"));
        unlockedAllocateLocalName(QLatin1String("data"));
        unlockedAllocateLocalName(QLatin1String("dateTime"));
        unlockedAllocateLocalName(QLatin1String("day-from-date"));
        unlockedAllocateLocalName(QLatin1String("day-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("days-from-duration"));
        unlockedAllocateLocalName(QLatin1String("deep-equal"));
        unlockedAllocateLocalName(QLatin1String("default"));
        unlockedAllocateLocalName(QLatin1String("default-collation"));
        unlockedAllocateLocalName(QLatin1String("distinct-values"));
        unlockedAllocateLocalName(QLatin1String("doc"));
        unlockedAllocateLocalName(QLatin1String("doc-available"));
        unlockedAllocateLocalName(QLatin1String("document"));
        unlockedAllocateLocalName(QLatin1String("document-uri"));
        unlockedAllocateLocalName(QLatin1String("element-available"));
        unlockedAllocateLocalName(QLatin1String("empty"));
        unlockedAllocateLocalName(QLatin1String("encode-for-uri"));
        unlockedAllocateLocalName(QLatin1String("ends-with"));
        unlockedAllocateLocalName(QLatin1String("error"));
        unlockedAllocateLocalName(QLatin1String("escape-html-uri"));
        unlockedAllocateLocalName(QLatin1String("exactly-one"));
        unlockedAllocateLocalName(QLatin1String("exists"));
        unlockedAllocateLocalName(QLatin1String("false"));
        unlockedAllocateLocalName(QLatin1String("floor"));
        unlockedAllocateLocalName(QLatin1String("function-available"));
        unlockedAllocateLocalName(QLatin1String("function-name"));
        unlockedAllocateLocalName(QLatin1String("generate-id"));
        unlockedAllocateLocalName(QLatin1String("generic-string-join"));
        unlockedAllocateLocalName(QLatin1String("hours-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("hours-from-duration"));
        unlockedAllocateLocalName(QLatin1String("hours-from-time"));
        unlockedAllocateLocalName(QLatin1String("id"));
        unlockedAllocateLocalName(QLatin1String("idref"));
        unlockedAllocateLocalName(QLatin1String("implicit-timezone"));
        unlockedAllocateLocalName(QLatin1String("index-of"));
        unlockedAllocateLocalName(QLatin1String("in-scope-prefixes"));
        unlockedAllocateLocalName(QLatin1String("insert-before"));
        unlockedAllocateLocalName(QLatin1String("iri-to-uri"));
        unlockedAllocateLocalName(QLatin1String("is-schema-aware"));
        unlockedAllocateLocalName(QLatin1String("key"));
        unlockedAllocateLocalName(QLatin1String("lang"));
        unlockedAllocateLocalName(QLatin1String("last"));
        unlockedAllocateLocalName(QLatin1String("local-name"));
        unlockedAllocateLocalName(QLatin1String("local-name-from-QName"));
        unlockedAllocateLocalName(QLatin1String("lower-case"));
        unlockedAllocateLocalName(QLatin1String("matches"));
        unlockedAllocateLocalName(QLatin1String("max"));
        unlockedAllocateLocalName(QLatin1String("min"));
        unlockedAllocateLocalName(QLatin1String("minutes-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("minutes-from-duration"));
        unlockedAllocateLocalName(QLatin1String("minutes-from-time"));
        unlockedAllocateLocalName(QLatin1String("month-from-date"));
        unlockedAllocateLocalName(QLatin1String("month-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("months-from-duration"));
        unlockedAllocateLocalName(QLatin1String("name"));
        unlockedAllocateLocalName(QLatin1String("namespace-uri"));
        unlockedAllocateLocalName(QLatin1String("namespace-uri-for-prefix"));
        unlockedAllocateLocalName(QLatin1String("namespace-uri-from-QName"));
        unlockedAllocateLocalName(QLatin1String("nilled"));
        unlockedAllocateLocalName(QLatin1String("node-name"));
        unlockedAllocateLocalName(QLatin1String("normalize-space"));
        unlockedAllocateLocalName(QLatin1String("normalize-unicode"));
        unlockedAllocateLocalName(QLatin1String("not"));
        unlockedAllocateLocalName(QLatin1String("number"));
        unlockedAllocateLocalName(QLatin1String("one-or-more"));
        unlockedAllocateLocalName(QLatin1String("position"));
        unlockedAllocateLocalName(QLatin1String("prefix-from-QName"));
        unlockedAllocateLocalName(QLatin1String("product-name"));
        unlockedAllocateLocalName(QLatin1String("product-version"));
        unlockedAllocateLocalName(QLatin1String("property-name"));
        unlockedAllocateLocalName(QLatin1String("QName"));
        unlockedAllocateLocalName(QLatin1String("remove"));
        unlockedAllocateLocalName(QLatin1String("replace"));
        unlockedAllocateLocalName(QLatin1String("resolve-QName"));
        unlockedAllocateLocalName(QLatin1String("resolve-uri"));
        unlockedAllocateLocalName(QLatin1String("reverse"));
        unlockedAllocateLocalName(QLatin1String("root"));
        unlockedAllocateLocalName(QLatin1String("round"));
        unlockedAllocateLocalName(QLatin1String("round-half-to-even"));
        unlockedAllocateLocalName(QLatin1String("seconds-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("seconds-from-duration"));
        unlockedAllocateLocalName(QLatin1String("seconds-from-time"));
        unlockedAllocateLocalName(QLatin1String("sourceValue"));
        unlockedAllocateLocalName(QLatin1String("starts-with"));
        unlockedAllocateLocalName(QLatin1String("static-base-uri"));
        unlockedAllocateLocalName(QLatin1String("string"));
        unlockedAllocateLocalName(QLatin1String("string-join"));
        unlockedAllocateLocalName(QLatin1String("string-length"));
        unlockedAllocateLocalName(QLatin1String("string-to-codepoints"));
        unlockedAllocateLocalName(QLatin1String("subsequence"));
        unlockedAllocateLocalName(QLatin1String("substring"));
        unlockedAllocateLocalName(QLatin1String("substring-after"));
        unlockedAllocateLocalName(QLatin1String("substring-before"));
        unlockedAllocateLocalName(QLatin1String("sum"));
        unlockedAllocateLocalName(QLatin1String("supports-backwards-compatibility"));
        unlockedAllocateLocalName(QLatin1String("supports-serialization"));
        unlockedAllocateLocalName(QLatin1String("system-property"));
        unlockedAllocateLocalName(QLatin1String("timezone-from-date"));
        unlockedAllocateLocalName(QLatin1String("timezone-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("timezone-from-time"));
        unlockedAllocateLocalName(QLatin1String("tokenize"));
        unlockedAllocateLocalName(QLatin1String("trace"));
        unlockedAllocateLocalName(QLatin1String("translate"));
        unlockedAllocateLocalName(QLatin1String("true"));
        unlockedAllocateLocalName(QLatin1String("type-available"));
        unlockedAllocateLocalName(QLatin1String("unordered"));
        unlockedAllocateLocalName(QLatin1String("unparsed-entity-public-id"));
        unlockedAllocateLocalName(QLatin1String("unparsed-entity-uri"));
        unlockedAllocateLocalName(QLatin1String("unparsed-text"));
        unlockedAllocateLocalName(QLatin1String("unparsed-text-available"));
        unlockedAllocateLocalName(QLatin1String("upper-case"));
        unlockedAllocateLocalName(QLatin1String("vendor"));
        unlockedAllocateLocalName(QLatin1String("vendor-url"));
        unlockedAllocateLocalName(QLatin1String("version"));
        unlockedAllocateLocalName(QLatin1String("xml"));
        unlockedAllocateLocalName(QLatin1String("xmlns"));
        unlockedAllocateLocalName(QLatin1String("year-from-date"));
        unlockedAllocateLocalName(QLatin1String("year-from-dateTime"));
        unlockedAllocateLocalName(QLatin1String("years-from-duration"));
        unlockedAllocateLocalName(QLatin1String("zero-or-one"));
        Q_ASSERT(m_localNames.count() == StandardLocalNameCount);
    }
}

QXmlName NamePool::allocateQName(const QString &uri, const QString &localName, const QString &prefix)
{
    QWriteLocker l(&lock);

    /*
    QString codepoints;
    for(int i = 0; i < localName.length(); ++i)
        codepoints.append(QString::number(localName.at(i).unicode()) + QLatin1Char(' '));

    pDebug() << Q_FUNC_INFO << localName << "codepoints:" << codepoints;
    */

    Q_ASSERT_X(QXmlUtils::isNCName(localName), Q_FUNC_INFO,
               qPrintable(QString::fromLatin1("'%1' is an invalid NCName.").arg(localName)));

    const QXmlName::NamespaceCode nsCode = unlockedAllocateNamespace(uri);
    const QXmlName::LocalNameCode localCode  = unlockedAllocateLocalName(localName);

    Q_ASSERT(prefix.isEmpty() || QXmlUtils::isNCName(prefix));
    const QXmlName::PrefixCode prefixCode = unlockedAllocatePrefix(prefix);

    return QXmlName(nsCode, localCode, prefixCode);
}

QXmlName NamePool::allocateBinding(const QString &prefix, const QString &uri)
{
    QWriteLocker l(&lock);

    Q_ASSERT_X(prefix.isEmpty() || QXmlUtils::isNCName(prefix), Q_FUNC_INFO,
               qPrintable(QString::fromLatin1("%1 is an invalid prefix.").arg(prefix)));
    const QXmlName::NamespaceCode nsCode = unlockedAllocateNamespace(uri);

    Q_ASSERT(prefix.isEmpty() || QXmlUtils::isNCName(prefix));
    const QXmlName::PrefixCode prefixCode = unlockedAllocatePrefix(prefix);

    return QXmlName(nsCode, StandardLocalNames::empty, prefixCode);
}

QXmlName::LocalNameCode NamePool::unlockedAllocateLocalName(const QString &ln)
{
    Q_ASSERT_X(QXmlUtils::isNCName(ln), Q_FUNC_INFO,
               qPrintable(QString::fromLatin1("Invalid local name: \"%1\"").arg(ln)));

    int indexInLocalNames = m_localNameMapping.value(ln, NoSuchValue);

    if(indexInLocalNames == NoSuchValue)
    {
        indexInLocalNames = m_localNames.count();
        m_localNames.append(ln);
        m_localNameMapping.insert(ln, indexInLocalNames);
    }

    return indexInLocalNames;
}

QXmlName::PrefixCode NamePool::unlockedAllocatePrefix(const QString &prefix)
{
    int indexInPrefixes = m_prefixMapping.value(prefix, NoSuchValue);

    if(indexInPrefixes == NoSuchValue)
    {
        indexInPrefixes = m_prefixes.count();
        m_prefixes.append(prefix);
        m_prefixMapping.insert(prefix, indexInPrefixes);
    }

    return indexInPrefixes;
}

QXmlName::NamespaceCode NamePool::unlockedAllocateNamespace(const QString &uri)
{
    int indexInURIs = m_namespaceMapping.value(uri, NoSuchValue);

    if(indexInURIs == NoSuchValue)
    {
        indexInURIs = m_namespaces.count();
        m_namespaces.append(uri);
        m_namespaceMapping.insert(uri, indexInURIs);
    }

    return indexInURIs;
}

const QString &NamePool::displayPrefix(const QXmlName::NamespaceCode nc) const
{
    switch(nc)
    {
        case StandardNamespaces::xmlns: return m_prefixes.at(StandardPrefixes::xmlns);
        case StandardNamespaces::local: return m_prefixes.at(StandardPrefixes::local);
        case StandardNamespaces::xs:    return m_prefixes.at(StandardPrefixes::xs);
        case StandardNamespaces::xml:   return m_prefixes.at(StandardPrefixes::xml);
        case StandardNamespaces::fn:    return m_prefixes.at(StandardPrefixes::fn);
        default:                        return m_prefixes.at(StandardPrefixes::empty);
    }
}

QString NamePool::displayName(const QXmlName qName) const
{
    QReadLocker l(&lock);

    if(qName.hasNamespace())
    {
        if(qName.namespaceURI() == StandardNamespaces::InternalXSLT)
            return QLatin1Char('#') + m_localNames.at(qName.localName());

        const QString &p = displayPrefix(qName.namespaceURI());

        if(p.isEmpty())
            return QLatin1Char('{') + m_namespaces.at(qName.namespaceURI()) + QLatin1Char('}') + toLexical(qName);
        else
            return p + QLatin1Char(':') + m_localNames.at(qName.localName());
    }
    else
        return m_localNames.at(qName.localName());
}

QString NamePool::toClarkName(const QXmlName &name) const
{
    if(name.isNull())
        return QLatin1String("QXmlName(null)");
    else
    {
        if(name.hasNamespace())
        {
            const QString ns(stringForNamespace(name.namespaceURI()));
            const QString p(stringForPrefix(name.prefix()));
            const QString l(stringForLocalName(name.localName()));

            return   QChar::fromLatin1('{')
                   + ns
                   + QChar::fromLatin1('}')
                   + (p.isEmpty() ? l : p + QChar::fromLatin1(':') + l);
        }
        else
            return stringForLocalName(name.localName());
    }
}

QXmlName NamePool::fromClarkName(const QString &clarkName)
{
    if(clarkName.isEmpty())
        return QXmlName();

    if(clarkName.at(0) == QLatin1Char('{'))
    {
        const int indexOfRight = clarkName.indexOf(QLatin1Char('}'));
        const QString qName(clarkName.right((clarkName.length() - indexOfRight) - 1));

        if(!XPathHelper::isQName(qName))
            return QXmlName();

        QString localName;
        QString prefix;

        XPathHelper::splitQName(qName, prefix, localName);

        return allocateQName(clarkName.mid(1, indexOfRight - 1),
                                         localName, prefix);
    }
    else
    {
        if(QXmlName::isNCName(clarkName))
            return allocateQName(QString(), clarkName);
        else
            return QXmlName();
    }
}
QT_END_NAMESPACE
