/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick 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 "qaccessiblequickitem_p.h"

#include <QtGui/qtextdocument.h>

#include "QtQuick/private/qquickitem_p.h"
#include "QtQuick/private/qquicktext_p.h"
#include "QtQuick/private/qquicktextinput_p.h"
#include "QtQuick/private/qquickaccessibleattached_p.h"
#include "QtQuick/qquicktextdocument.h"
QT_BEGIN_NAMESPACE

#if QT_CONFIG(accessibility)

QAccessibleQuickItem::QAccessibleQuickItem(QQuickItem *item)
    : QAccessibleObject(item), m_doc(textDocument())
{
}

QWindow *QAccessibleQuickItem::window() const
{
    return item()->window();
}

int QAccessibleQuickItem::childCount() const
{
    return childItems().count();
}

QRect QAccessibleQuickItem::rect() const
{
    const QRect r = itemScreenRect(item());
    return r;
}

QRect QAccessibleQuickItem::viewRect() const
{
    // ### no window in some cases.
    if (!item()->window()) {
        return QRect();
    }

    QQuickWindow *window = item()->window();
    QPoint screenPos = window->mapToGlobal(QPoint(0,0));
    return QRect(screenPos, window->size());
}


bool QAccessibleQuickItem::clipsChildren() const
{
    return static_cast<QQuickItem *>(item())->clip();
}

QAccessibleInterface *QAccessibleQuickItem::childAt(int x, int y) const
{
    if (item()->clip()) {
        if (!rect().contains(x, y))
            return nullptr;
    }

    const QList<QQuickItem*> kids = accessibleUnignoredChildren(item(), true);
    for (int i = kids.count() - 1; i >= 0; --i) {
        QAccessibleInterface *childIface = QAccessible::queryAccessibleInterface(kids.at(i));
        if (QAccessibleInterface *childChild = childIface->childAt(x, y))
            return childChild;
        if (childIface && !childIface->state().invisible) {
            if (childIface->rect().contains(x, y))
                return childIface;
        }
    }

    return nullptr;
}

QAccessibleInterface *QAccessibleQuickItem::parent() const
{
    QQuickItem *parent = item()->parentItem();
    QQuickWindow *window = item()->window();
    QQuickItem *ci = window ? window->contentItem() : nullptr;
    while (parent && !QQuickItemPrivate::get(parent)->isAccessible && parent != ci)
        parent = parent->parentItem();

    if (parent) {
        if (parent == ci) {
            // Jump out to the scene widget if the parent is the root item.
            // There are two root items, QQuickWindow::rootItem and
            // QQuickView::declarativeRoot. The former is the true root item,
            // but is not a part of the accessibility tree. Check if we hit
            // it here and return an interface for the scene instead.
            return QAccessible::queryAccessibleInterface(window);
        } else {
            while (parent && !parent->d_func()->isAccessible)
                parent = parent->parentItem();
            return QAccessible::queryAccessibleInterface(parent);
        }
    }
    return nullptr;
}

QAccessibleInterface *QAccessibleQuickItem::child(int index) const
{
    QList<QQuickItem *> children = childItems();

    if (index < 0 || index >= children.count())
        return nullptr;

    QQuickItem *child = children.at(index);
    return QAccessible::queryAccessibleInterface(child);
}

int QAccessibleQuickItem::indexOfChild(const QAccessibleInterface *iface) const
{
    QList<QQuickItem*> kids = childItems();
    return kids.indexOf(static_cast<QQuickItem*>(iface->object()));
}

static void unignoredChildren(QQuickItem *item, QList<QQuickItem *> *items, bool paintOrder)
{
    const QList<QQuickItem*> childItems = paintOrder ? QQuickItemPrivate::get(item)->paintOrderChildItems()
                                               : item->childItems();
    for (QQuickItem *child : childItems) {
        if (QQuickItemPrivate::get(child)->isAccessible) {
            items->append(child);
        } else {
            unignoredChildren(child, items, paintOrder);
        }
    }
}

QList<QQuickItem *> accessibleUnignoredChildren(QQuickItem *item, bool paintOrder)
{
    QList<QQuickItem *> items;
    unignoredChildren(item, &items, paintOrder);
    return items;
}

QList<QQuickItem *> QAccessibleQuickItem::childItems() const
{
    return accessibleUnignoredChildren(item());
}

QAccessible::State QAccessibleQuickItem::state() const
{
    QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item());
    if (!attached)
        return QAccessible::State();

    QAccessible::State state = attached->state();

    QRect viewRect_ = viewRect();
    QRect itemRect = rect();

    if (viewRect_.isNull() || itemRect.isNull() || !item()->window() || !item()->window()->isVisible() ||!item()->isVisible() || qFuzzyIsNull(item()->opacity()))
        state.invisible = true;
    if (!viewRect_.intersects(itemRect))
        state.offscreen = true;
    if ((role() == QAccessible::CheckBox || role() == QAccessible::RadioButton) && object()->property("checked").toBool())
        state.checked = true;
    if (item()->activeFocusOnTab() || role() == QAccessible::EditableText)
        state.focusable = true;
    if (item()->hasActiveFocus())
        state.focused = true;
    if (role() == QAccessible::EditableText)
        if (auto ti = qobject_cast<QQuickTextInput *>(item()))
            state.passwordEdit = ti->echoMode() != QQuickTextInput::Normal;
    return state;
}

QAccessible::Role QAccessibleQuickItem::role() const
{
    // Workaround for setAccessibleRole() not working for
    // Text items. Text items are special since they are defined
    // entirely from C++ (setting the role from QML works.)

    QAccessible::Role role = QAccessible::NoRole;
    if (item())
        role = QQuickItemPrivate::get(item())->accessibleRole();
    if (role == QAccessible::NoRole) {
        if (qobject_cast<QQuickText*>(const_cast<QQuickItem *>(item())))
            role = QAccessible::StaticText;
        else
            role = QAccessible::Client;
    }

    return role;
}

bool QAccessibleQuickItem::isAccessible() const
{
    return item()->d_func()->isAccessible;
}

QStringList QAccessibleQuickItem::actionNames() const
{
    QStringList actions;
    switch (role()) {
    case QAccessible::PushButton:
        actions << QAccessibleActionInterface::pressAction();
        break;
    case QAccessible::RadioButton:
    case QAccessible::CheckBox:
        actions << QAccessibleActionInterface::toggleAction()
                << QAccessibleActionInterface::pressAction();
        break;
    case QAccessible::Slider:
    case QAccessible::SpinBox:
    case QAccessible::ScrollBar:
        actions << QAccessibleActionInterface::increaseAction()
                << QAccessibleActionInterface::decreaseAction();
        break;
    default:
        break;
    }
    if (state().focusable)
        actions.append(QAccessibleActionInterface::setFocusAction());

    // ### The following can lead to duplicate action names.
    if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
        attached->availableActions(&actions);
    return actions;
}

void QAccessibleQuickItem::doAction(const QString &actionName)
{
    bool accepted = false;
    if (actionName == QAccessibleActionInterface::setFocusAction()) {
        item()->forceActiveFocus();
        accepted = true;
    }
    if (QQuickAccessibleAttached *attached = QQuickAccessibleAttached::attachedProperties(item()))
        accepted = attached->doAction(actionName);

    if (accepted)
        return;
    // Look for and call the accessible[actionName]Action() function on the item.
    // This allows for overriding the default action handling.
    const QByteArray functionName = "accessible" + actionName.toLatin1() + "Action";
    if (object()->metaObject()->indexOfMethod(QByteArray(functionName + "()")) != -1) {
        QMetaObject::invokeMethod(object(), functionName);
        return;
    }

    // Role-specific default action handling follows. Items are expected to provide
    // properties according to role conventions. These will then be read and/or updated
    // by the accessibility system.
    //   Checkable roles   : checked
    //   Value-based roles : (via the value interface: value, minimumValue, maximumValue), stepSize
    switch (role()) {
    case QAccessible::RadioButton:
    case QAccessible::CheckBox: {
        QVariant checked = object()->property("checked");
        if (checked.isValid()) {
            if (actionName == QAccessibleActionInterface::toggleAction() ||
                    actionName == QAccessibleActionInterface::pressAction()) {

                object()->setProperty("checked",  QVariant(!checked.toBool()));
            }
        }
        break;
    }
    case QAccessible::Slider:
    case QAccessible::SpinBox:
    case QAccessible::Dial:
    case QAccessible::ScrollBar: {
        if (actionName != QAccessibleActionInterface::increaseAction() &&
                actionName != QAccessibleActionInterface::decreaseAction())
            break;

        // Update the value using QAccessibleValueInterface, respecting
        // the minimum and maximum value (if set). Also check for and
        // use the "stepSize" property on the item
        if (QAccessibleValueInterface *valueIface = valueInterface()) {
            QVariant valueV = valueIface->currentValue();
            qreal newValue = valueV.toReal();

            QVariant stepSizeV = object()->property("stepSize");
            qreal stepSize = stepSizeV.isValid() ? stepSizeV.toReal() : qreal(1.0);
            if (actionName == QAccessibleActionInterface::increaseAction()) {
                newValue += stepSize;
            } else {
                newValue -= stepSize;
            }

            QVariant minimumValueV = valueIface->minimumValue();
            if (minimumValueV.isValid()) {
                newValue = qMax(newValue, minimumValueV.toReal());
            }
            QVariant maximumValueV = valueIface->maximumValue();
            if (maximumValueV.isValid()) {
                newValue = qMin(newValue, maximumValueV.toReal());
            }

            valueIface->setCurrentValue(QVariant(newValue));
        }
        break;
    }
    default:
        break;
    }
}

QStringList QAccessibleQuickItem::keyBindingsForAction(const QString &actionName) const
{
    Q_UNUSED(actionName)
    return QStringList();
}

QString QAccessibleQuickItem::text(QAccessible::Text textType) const
{
    // handles generic behavior not specific to an item
    switch (textType) {
    case QAccessible::Name: {
        QVariant accessibleName = QQuickAccessibleAttached::property(object(), "name");
        if (!accessibleName.isNull())
            return accessibleName.toString();
        break;}
    case QAccessible::Description: {
        QVariant accessibleDecription = QQuickAccessibleAttached::property(object(), "description");
        if (!accessibleDecription.isNull())
            return accessibleDecription.toString();
        break;}
#ifdef Q_ACCESSIBLE_QUICK_ITEM_ENABLE_DEBUG_DESCRIPTION
    case QAccessible::DebugDescription: {
        QString debugString;
        debugString = QString::fromLatin1(object()->metaObject()->className()) + QLatin1Char(' ');
        debugString += isAccessible() ? QLatin1String("enabled") : QLatin1String("disabled");
        return debugString;
        break; }
#endif
    case QAccessible::Value:
    case QAccessible::Help:
    case QAccessible::Accelerator:
    default:
        break;
    }

    // the following block handles item-specific behavior
    if (role() == QAccessible::EditableText) {
        if (textType == QAccessible::Value) {
            if (QTextDocument *doc = textDocument()) {
                return doc->toPlainText();
            }
            QVariant text = object()->property("text");
            return text.toString();
        }
    }

    return QString();
}

void QAccessibleQuickItem::setText(QAccessible::Text textType, const QString &text)
{
    if (role() != QAccessible::EditableText)
        return;
    if (textType != QAccessible::Value)
        return;

    if (QTextDocument *doc = textDocument()) {
        doc->setPlainText(text);
        return;
    }
    auto textPropertyName = "text";
    if (object()->metaObject()->indexOfProperty(textPropertyName) >= 0)
        object()->setProperty(textPropertyName, text);
}

void *QAccessibleQuickItem::interface_cast(QAccessible::InterfaceType t)
{
    QAccessible::Role r = role();
    if (t == QAccessible::ActionInterface)
        return static_cast<QAccessibleActionInterface*>(this);
    if (t == QAccessible::ValueInterface &&
           (r == QAccessible::Slider ||
            r == QAccessible::SpinBox ||
            r == QAccessible::Dial ||
            r == QAccessible::ScrollBar))
       return static_cast<QAccessibleValueInterface*>(this);

    if (t == QAccessible::TextInterface &&
            (r == QAccessible::EditableText))
        return static_cast<QAccessibleTextInterface*>(this);

    return QAccessibleObject::interface_cast(t);
}

QVariant QAccessibleQuickItem::currentValue() const
{
    return item()->property("value");
}

void QAccessibleQuickItem::setCurrentValue(const QVariant &value)
{
    item()->setProperty("value", value);
}

QVariant QAccessibleQuickItem::maximumValue() const
{
    return item()->property("maximumValue");
}

QVariant QAccessibleQuickItem::minimumValue() const
{
    return item()->property("minimumValue");
}

QVariant QAccessibleQuickItem::minimumStepSize() const
{
    return item()->property("stepSize");
}

/*!
  \internal
  Shared between QAccessibleQuickItem and QAccessibleQuickView
*/
QRect itemScreenRect(QQuickItem *item)
{
    // ### no window in some cases.
    // ### Should we really check for 0 opacity?
    if (!item->window() ||!item->isVisible() || qFuzzyIsNull(item->opacity())) {
        return QRect();
    }

    QSize itemSize((int)item->width(), (int)item->height());
    // ### If the bounding rect fails, we first try the implicit size, then we go for the
    // parent size. WE MIGHT HAVE TO REVISIT THESE FALLBACKS.
    if (itemSize.isEmpty()) {
        itemSize = QSize((int)item->implicitWidth(), (int)item->implicitHeight());
        if (itemSize.isEmpty() && item->parentItem())
            // ### Seems that the above fallback is not enough, fallback to use the parent size...
            itemSize = QSize((int)item->parentItem()->width(), (int)item->parentItem()->height());
    }

    QPointF scenePoint = item->mapToScene(QPointF(0, 0));
    QPoint screenPos = item->window()->mapToGlobal(scenePoint.toPoint());
    return QRect(screenPos, itemSize);
}

QTextDocument *QAccessibleQuickItem::textDocument() const
{
    QVariant docVariant = item()->property("textDocument");
    if (docVariant.canConvert<QQuickTextDocument*>()) {
        QQuickTextDocument *qqdoc = docVariant.value<QQuickTextDocument*>();
        return qqdoc->textDocument();
    }
    return nullptr;
}

int QAccessibleQuickItem::characterCount() const
{
    if (m_doc) {
        QTextCursor cursor = QTextCursor(m_doc);
        cursor.movePosition(QTextCursor::End);
        return cursor.position();
    }
    return text(QAccessible::Value).size();
}

int QAccessibleQuickItem::cursorPosition() const
{
    QVariant pos = item()->property("cursorPosition");
    return pos.toInt();
}

void QAccessibleQuickItem::setCursorPosition(int position)
{
    item()->setProperty("cursorPosition", position);
}

QString QAccessibleQuickItem::text(int startOffset, int endOffset) const
{
    if (m_doc) {
        QTextCursor cursor = QTextCursor(m_doc);
        cursor.setPosition(startOffset);
        cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
        return cursor.selectedText();
    }
    return text(QAccessible::Value).mid(startOffset, endOffset - startOffset);
}

QString QAccessibleQuickItem::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
                                 int *startOffset, int *endOffset) const
{
    Q_ASSERT(startOffset);
    Q_ASSERT(endOffset);

    if (m_doc) {
        QTextCursor cursor = QTextCursor(m_doc);
        cursor.setPosition(offset);
        QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
        cursor.setPosition(boundaries.first - 1);
        boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);

        *startOffset = boundaries.first;
        *endOffset = boundaries.second;

        return text(boundaries.first, boundaries.second);
    } else {
        return QAccessibleTextInterface::textBeforeOffset(offset, boundaryType, startOffset, endOffset);
    }
}

QString QAccessibleQuickItem::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
                                int *startOffset, int *endOffset) const
{
    Q_ASSERT(startOffset);
    Q_ASSERT(endOffset);

    if (m_doc) {
        QTextCursor cursor = QTextCursor(m_doc);
        cursor.setPosition(offset);
        QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
        cursor.setPosition(boundaries.second);
        boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);

        *startOffset = boundaries.first;
        *endOffset = boundaries.second;

        return text(boundaries.first, boundaries.second);
    } else {
        return QAccessibleTextInterface::textAfterOffset(offset, boundaryType, startOffset, endOffset);
    }
}

QString QAccessibleQuickItem::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
                             int *startOffset, int *endOffset) const
{
    Q_ASSERT(startOffset);
    Q_ASSERT(endOffset);

    if (m_doc) {
        QTextCursor cursor = QTextCursor(m_doc);
        cursor.setPosition(offset);
        QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);

        *startOffset = boundaries.first;
        *endOffset = boundaries.second;
        return text(boundaries.first, boundaries.second);
    } else {
        return QAccessibleTextInterface::textAtOffset(offset, boundaryType, startOffset, endOffset);
    }
}

void QAccessibleQuickItem::selection(int selectionIndex, int *startOffset, int *endOffset) const
{
    if (selectionIndex == 0) {
        *startOffset = item()->property("selectionStart").toInt();
        *endOffset = item()->property("selectionEnd").toInt();
    } else {
        *startOffset = 0;
        *endOffset = 0;
    }
}

int QAccessibleQuickItem::selectionCount() const
{
    if (item()->property("selectionStart").toInt() != item()->property("selectionEnd").toInt())
        return 1;
    return 0;
}

void QAccessibleQuickItem::addSelection(int /* startOffset */, int /* endOffset */)
{

}
void QAccessibleQuickItem::removeSelection(int /* selectionIndex */)
{

}
void QAccessibleQuickItem::setSelection(int /* selectionIndex */, int /* startOffset */, int /* endOffset */)
{

}


#endif // accessibility

QT_END_NAMESPACE
