/****************************************************************************
**
** 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 "qbuiltintypes_p.h"
#include "qcommonsequencetypes_p.h"
#include "qgenericsequencetype_p.h"
#include "qnodesort_p.h"
#include "qpatternistlocale_p.h"
#include "qsequencemappingiterator_p.h"
#include "qtypechecker_p.h"

#include "qpath_p.h"

QT_BEGIN_NAMESPACE

using namespace QPatternist;

Path::Path(const Expression::Ptr &operand1,
           const Expression::Ptr &operand2,
           const Kind kind) : PairContainer(operand1, operand2)
                            , m_hasCreatedSorter(kind != RegularPath)
                            , m_isLast(false)
                            , m_checkXPTY0018(kind == RegularPath)
                            , m_kind(kind)
{
}

/*! \internal */
Path::~Path()
{
}

Item::Iterator::Ptr Path::mapToSequence(const Item &item,
                                        const DynamicContext::Ptr &context) const
{
    /* item is the focus here. That is in <e/>/1, item is <e/>. However,
     * we don't use it, since the context item is accessed through
     * DynamicContext::focusIterator() and friends. */
    Q_ASSERT(item);
    Q_UNUSED(item); /* Needed when compiling in release mode. */
    return m_operand2->evaluateSequence(context);
}

Item::Iterator::Ptr Path::evaluateSequence(const DynamicContext::Ptr &context) const
{
    /* Note, we use the old context for m_operand1. */
    const Item::Iterator::Ptr source(m_operand1->evaluateSequence(context));

    const DynamicContext::Ptr focus(context->createFocus());
    focus->setFocusIterator(source);

    const Item::Iterator::Ptr result(makeSequenceMappingIterator<Item>(ConstPtr(this), source, focus));

    if(m_checkXPTY0018)
    {
        /* This is an expensive code path, but it should happen very rarely. */

        enum FoundItem
        {
            FoundNone,
            FoundNode,
            FoundAtomicValue
        } hasFound = FoundNone;

        Item::List whenChecked;

        Item next(result->next());

        while(next)
        {
            const FoundItem found = next.isAtomicValue() ? FoundAtomicValue : FoundNode;

            if(hasFound != FoundNone && hasFound != found)
            {
                /* It's an atomic value and we've already found a node. Mixed content. */
                context->error(QtXmlPatterns::tr("The last step in a path must contain either nodes "
                                                 "or atomic values. It cannot be a mixture between the two."),
                               ReportContext::XPTY0018, this);
            }
            else
                hasFound = found;

            whenChecked.append(next);
            next = result->next();
        }

        return makeListIterator(whenChecked);
    }
    else
        return result;
}

Item Path::evaluateSingleton(const DynamicContext::Ptr &context) const
{
    /* This function is called if both operands' cardinality is exactly-one. Therefore
     * we manually go forward in the focus by calling next().
     *
     * We don't check for XPTY0018, only in evaluateSequence(), since if we're guaranteed
     * to evaluate to one item, we can only evaluate to one node or one atomic value.
     */

    /* Note, we use the old context for m_operand1. */
    const Item::Iterator::Ptr source(m_operand1->evaluateSequence(context));

    const DynamicContext::Ptr focus(context->createFocus());
    focus->setFocusIterator(source);

    /* This test is needed because if the focus is empty, we don't want to(nor can't) evaluate
     * the next step. */
    // TODO Why are we at all invoked then?
    if(source->next())
        return m_operand2->evaluateSingleton(focus);
    else
        return Item();
}

void Path::evaluateToSequenceReceiver(const DynamicContext::Ptr &context) const
{
    /* Note, we use the old context for m_operand1. */
    const Item::Iterator::Ptr source(m_operand1->evaluateSequence(context));

    const DynamicContext::Ptr focus(context->createFocus());
    focus->setFocusIterator(source);

    while(source->next())
        m_operand2->evaluateToSequenceReceiver(focus);
}

Expression::Ptr Path::compress(const StaticContext::Ptr &context)
{
    const Expression::Ptr me(PairContainer::compress(context));

    /* "./expr" is by now equal to "expr" since we've done
     * focus/type checks, and a node sorter has been inserted. */
    if(m_operand1->is(IDContextItem))
        return m_operand2;

    /* We do this as late as we can, such that we pick up the most recent type
     * from the operand. */
    if(m_isLast && m_kind != XSLTForEach && m_operand2->staticType()->itemType() == BuiltinTypes::item)
        m_checkXPTY0018 = true;

    return me;
}

Expression::Ptr Path::typeCheck(const StaticContext::Ptr &context,
                                const SequenceType::Ptr &reqType)
{
    m_operand2->announceFocusType(newFocusType());

    /* Here we apply the function conversion first, and with the error code
     * that we want -- XPTY0019 instead of XPTY0004. Unfortunately
     * PairContainer::typeCheck() will do the type check again, which is
     * redundant in the case of when we're not XSLTForEach.
     *
     * If we're XSLTForEach, it means we're a synthetic "map" expression for
     * implementing various XSL-T expressions, and hence don't have the
     * constraint of XPTY0019.
     *
     * It's important that typeCheck() is run for the operands(of course), and the call to
     * PairContainer::typeCheck() ensures that below, in the case that we're XSL-T code.
     *
     * The type we expect, CommonSequenceTypes::ZeroOrMoreNodes() needs to be in sync with
     * what we return in expectedOperandTypes(). */
    if(m_kind != XSLTForEach)
    {
        m_operand1 = TypeChecker::applyFunctionConversion(m_operand1,
                                                          CommonSequenceTypes::ZeroOrMoreNodes,
                                                          context,
                                                          m_kind == ForApplyTemplate ? ReportContext::XTTE0520
                                                                                     : ReportContext::XPTY0019);
    }

    /* If our step ends with atomic values, we cannot sort.
     *
     * We must smack the NodeSortExpression ontop before calling typeCheck(), since the latter
     * may insert an Atomizer, as possibly mandated by reqType. By doing it after, the Atomizer
     * will be a parent to NodeSortExpression, as opposed to a child.
     */
    if(!m_hasCreatedSorter)
    {
        m_hasCreatedSorter = true;

        return NodeSortExpression::wrapAround(Expression::Ptr(this), context)->typeCheck(context, reqType);
    }
    else
        return PairContainer::typeCheck(context, reqType);
}

SequenceType::List Path::expectedOperandTypes() const
{
    SequenceType::List result;

    /* This value needs to be in sync with what we pass to
     * applyFunctionConversion() in typeCheck() above.
     *
     * We don't have the XPTY0019 restriction when we're synthetic XSL-T code.
     */
    if(m_kind == XSLTForEach)
        result.append(CommonSequenceTypes::ZeroOrMoreItems);
    else
        result.append(CommonSequenceTypes::ZeroOrMoreNodes);

    result.append(CommonSequenceTypes::ZeroOrMoreItems);
    return result;
}

SequenceType::Ptr Path::staticType() const
{
    const SequenceType::Ptr opType(m_operand2->staticType());

    /* For each parent step, we evaluate the child step. So multiply the two
     * cardinalities. */
    return makeGenericSequenceType(opType->itemType(),
                                   m_operand1->staticType()->cardinality() * opType->cardinality());
}

ExpressionVisitorResult::Ptr Path::accept(const ExpressionVisitor::Ptr &visitor) const
{
    return visitor->visit(this);
}

Expression::Properties Path::properties() const
{
    return CreatesFocusForLast | ((m_operand1->properties() | m_operand2->properties()) & (RequiresCurrentItem | DisableElimination));
}

ItemType::Ptr Path::newFocusType() const
{
    return m_operand1->staticType()->itemType();
}

Expression::ID Path::id() const
{
    return IDPath;
}

QT_END_NAMESPACE
