/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "accessibilityscenemanager.h"

AccessibilitySceneManager::AccessibilitySceneManager()
{
    m_window = 0;
    m_view = 0;
    m_scene = 0;
    m_rootItem = 0;
    m_optionsWidget = 0;
    m_selectedObject = 0;
}

void AccessibilitySceneManager::populateAccessibilityScene()
{
    m_scene->clear();
    m_graphicsItems.clear();

    QAccessibleInterface * rootInterface = m_window->accessibleRoot();
    if (!rootInterface)
        return;

    populateAccessibilityScene(rootInterface, m_scene);
}

void AccessibilitySceneManager::updateAccessibilitySceneItemFlags()
{
    qDebug() << "update";
    foreach (QObject *object, m_graphicsItems.keys()) {
        if (!object)
            continue;
        QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(object);
        if (!interface)
            continue;
        updateItemFlags(m_graphicsItems.value(object), interface);
    }
}

void AccessibilitySceneManager::populateAccessibilityTreeScene()
{
    m_treeScene->clear();
    QAccessibleInterface * rootInterface = m_window->accessibleRoot();
    if (!rootInterface) {
        qWarning("QWindow::accessibleRoot returned 0");
        return;
    }

    populateAccessibilityTreeScene(rootInterface);
}

void AccessibilitySceneManager::handleUpdate(QAccessibleEvent *event)
{
    QObject *object = event->object();
    QAccessible::Event type = event->type();

    QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(object);
    if (!interface)
        return;

    QString name = interface->text(QAccessible::Name);

    if (type == QAccessible::ObjectCreated) {
  //      qDebug() << "ObjectCreated" << object << name;
        populateAccessibilityScene(interface, m_scene);
    }

    QGraphicsRectItem *item = m_graphicsItems.value(object);

    if (!item) {
//        qDebug() << "populateAccessibilityScene failed for" << object;
        return;
    }

    if (type == QAccessible::LocationChanged) {

        //if (name.startsWith("List"))
            qDebug() << "locationChange" << object << name << interface->rect();

        updateItem(item, interface);
        for (int i = 0; i < interface->childCount(); ++i) {
           QAccessibleInterface *child = interface->child(i);
           if (child) {
               updateItem(m_graphicsItems.value(child->object()), child);
            }
        }

    } else if (type == QAccessible::ObjectDestroyed) {
//        qDebug() << "ObjectDestroyed" << object << name;
        delete m_graphicsItems.value(object);
        m_graphicsItems.remove(object);
        m_animatedObjects.remove(object);
        if (object == m_selectedObject) {
            m_selectedObject = 0;
        }
    } else if (type == QAccessible::ObjectHide) {
//        qDebug() << "ObjectCreated Hide" << object;
        updateItemFlags(item, interface);
    } else if (type == QAccessible::ObjectShow) {
//        qDebug() << "ObjectCreated Show" << object;
        updateItemFlags(item, interface);
    } else if (type == QAccessible::ScrollingStart) {
        qDebug() << "ObjectCreated ScrollingStart" << object;
        for (int i = 0; i < interface->childCount(); ++i) {
            QAccessibleInterface *child = interface->child(i);
            if (child) {
                m_animatedObjects.insert(child->object());
            }
        }
    } else if (type == QAccessible::ScrollingEnd) {
        // qDebug() << "ObjectCreated ScrollingEnd" << object;
        foreach (QObject *object, m_animatedObjects) {
            updateItem(m_graphicsItems.value(object), interface);
        }
        m_animatedObjects.clear();

    } else {
//        qDebug() << "other update" << object;
    }
}

void AccessibilitySceneManager::setSelected(QObject *object)
{
    m_scene->update(); // scedule update

    // clear existing selection
    if (m_selectedObject) {
        QObject *previousSelectedObject = m_selectedObject;
        m_selectedObject = 0;
        updateItem(previousSelectedObject);
    }

    m_selectedObject = object;
    updateItem(object);

    populateAccessibilityTreeScene();
}

void AccessibilitySceneManager::changeScale(int)
{
    // No QGraphicsView::setScale :(

    //m_view->scale(scale / 10.0, scale / 10.0);
    //if (m_rootItem)
    //    m_view->ensureVisible(m_rootItem);
}

void AccessibilitySceneManager::updateItems(QObject *root)
{
    QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(root);
    if (!interface)
        return;
    updateItem(m_graphicsItems.value(root), interface);

    for (int i = 0; i < interface->childCount(); ++i) {
        QAccessibleInterface *child = interface->child(i);
        updateItems(child->object());
    }
}

void AccessibilitySceneManager::updateItem(QObject *object)
{
    if (!object)
        return;

    QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(object);
    if (!interface)
        return;

    updateItem(m_graphicsItems.value(object), interface);
}

void AccessibilitySceneManager::updateItem(QGraphicsRectItem *item, QAccessibleInterface *interface)
{
    if (!item)
        return;

    QRect rect = interface->rect();
    item->setPos(rect.topLeft());
    item->setRect(QRect(QPoint(0,0), rect.size()));

    updateItemFlags(item, interface);
}

void AccessibilitySceneManager::updateItemFlags(QGraphicsRectItem *item, QAccessibleInterface *interface)
{
  //  qDebug() << "udpateItemFlags" << interface << interface->object();

    bool shouldShow = true;

    if (m_optionsWidget->hideInvisibleItems()) {
        if (isHidden(interface)) {
            shouldShow = false;
        }
    }

    if (m_optionsWidget->hideOffscreenItems()) {
        if (interface->state().offscreen) {
            shouldShow = false;
        }
    }

    if (m_optionsWidget->hidePaneItems()) {
        if (interface->role() & QAccessible::Pane) {
            shouldShow = false;
        }
    }

    if (m_optionsWidget->hideNullObjectItems()) {
        if (interface->object() == 0) {
            shouldShow = false;
        }
    }

    if (m_optionsWidget->hideNullRectItems()) {
        if (interface->rect().isNull()) {
            shouldShow = false;
        }
    }

    item->setVisible(shouldShow);

    if (interface->object() && interface->object() == m_selectedObject)
        item->setBrush(QColor(Qt::yellow));
    else
        item->setBrush(QColor(Qt::white));

    m_view->update();
}

QGraphicsRectItem * AccessibilitySceneManager::processInterface(QAccessibleInterface * interface, QGraphicsScene *scene)
{
    // Process this interface

    QGraphicsRectItem * item = new QGraphicsRectItem();
    scene->addItem(item);
    if (!m_rootItem)
        m_rootItem = item;

    QString name = interface->text(QAccessible::Name);
    QString description; // = interface->text(QAccessibleInterface::Description, child);
    QString role = translateRole(interface->role());
    int childCount = interface->childCount();

    /* qDebug() << "name:" << name << "local pos" <<
               interface->rect(0) << "description" << description << "childCount" << childCount;
*/

    updateItem(item, interface);

    QGraphicsSimpleTextItem * textItem = new QGraphicsSimpleTextItem();
    textItem->setParentItem(item);
    textItem->setPos(QPoint(5, 5));

    QString text;
    text.append("Name: " + name + " ");
    if (!description.isEmpty())
        text.append("Description: " + description + " ");
    text.append("Role: " + role + " ");
    if (childCount > 0)
        text.append("ChildCount: " + QString::number(childCount) + " ");
    textItem->setText(text);

    QFont font;
    font.setPointSize(10);
 //   font.setPointSize(14);
    textItem->setFont(font);

    return item;
}

void AccessibilitySceneManager::populateAccessibilityScene(QAccessibleInterface * interface, QGraphicsScene *scene)
{
    if (!interface)
        return;

    QGraphicsRectItem *item = processInterface(interface, scene);

    QObject *object = interface->object();
    if (object) {
        m_graphicsItems.insert(object, item);
    }

    for (int i = 0; i < interface->childCount(); ++i) {
        QAccessibleInterface *child = interface->child(i);
        updateItems(child->object());
        populateAccessibilityScene(child, scene);
    }
}

AccessibilitySceneManager::TreeItem AccessibilitySceneManager::computeLevels(QAccessibleInterface * interface, int level)
{
    if (interface == 0)
        return TreeItem();

    TreeItem currentLevel;

    int usedChildren = 0;
    for (int i = 0; i < interface->childCount(); ++i) {
        QAccessibleInterface *child = interface->child(i);
        if (child != 0) {
            ++usedChildren;
            TreeItem childLevel = computeLevels(child, level + 1);
            currentLevel.children.append(childLevel);
            currentLevel.width += childLevel.width + m_treeItemHorizontalPadding;
        }
    }

    // leaf node case
    if (usedChildren == 0) {
        currentLevel.width = m_treeItemWidth + m_treeItemHorizontalPadding;
    }

    // capture information:
    currentLevel.name = interface->text(QAccessible::Name);
    currentLevel.description += interface->text(QAccessible::DebugDescription);
    currentLevel.role = translateRole(interface->role());
    currentLevel.rect = interface->rect();
    currentLevel.state = interface->state();
    currentLevel.object = interface->object();

    return currentLevel;
}

void AccessibilitySceneManager::populateAccessibilityTreeScene(QAccessibleInterface * interface)
{
    if (!interface)
        return;

    // set some layout metrics:
    m_treeItemWidth = 90;
    m_treeItemHorizontalPadding = 10;
    m_treeItemHeight = 60;
    m_treeItemVerticalPadding = 30;

    // We want to draw the accessibility hiearchy as a vertical
    // tree, growing from the root node at the top.

    // First, figure out the number of levels and the width of each level:
    m_rootTreeItem = computeLevels(interface, 0);

    // create graphics items for each tree item
    addGraphicsItems(m_rootTreeItem, 0, 0);
}

void AccessibilitySceneManager::addGraphicsItems(AccessibilitySceneManager::TreeItem item, int row, int xPos)
{
    //qDebug() << "add graphics item" << row << item.name << item.role << xPos << item.width << item.children.count();

    int yPos = row * (m_treeItemHeight + m_treeItemVerticalPadding);

    // Process this interface
    QGraphicsRectItem * graphicsItem = new QGraphicsRectItem();
    graphicsItem->setPos(xPos, yPos);
    graphicsItem->setRect(0, 0, m_treeItemWidth, m_treeItemHeight);
    graphicsItem->setFlag(QGraphicsItem::ItemClipsChildrenToShape);

    if (item.object && item.object == m_selectedObject)
        graphicsItem->setBrush(QColor(Qt::yellow));
    else
        graphicsItem->setBrush(QColor(Qt::white));

    if (item.state.offscreen) {
        QPen linePen;
        linePen.setStyle(Qt::DashLine);
        graphicsItem->setPen(linePen);
    }

    m_treeScene->addItem(graphicsItem);

    QGraphicsTextItem * textItem = new QGraphicsTextItem();
    textItem->setParentItem(graphicsItem);
    textItem->setPos(QPoint(0, 0));

    QFont font;
    font.setPointSize(8);
    textItem->setFont(font);

    QString text;
    text += item.name + "\n";
    text += item.role + "\n";
    text += item.description.split(" ", QString::SkipEmptyParts).join("\n") + "\n";
    text += "P:" + QString::number(item.rect.x()) + " " + QString::number(item.rect.y()) + " ";
    text += "S:" + QString::number(item.rect.width()) + " " + QString::number(item.rect.height()) + "\n";

    textItem->setPlainText(text);

    // recurse to children
    int childIndex = 0;
    int childCount = item.children.count();
    int segmentSize = item.width / qMax(1, childCount);
    int segmentCenterOffset = segmentSize / 2;
    int segmentsStart = xPos - (item.width / 2);
    foreach (TreeItem child, item.children) {
        // spread the children out, covering the width, centered on xPos
        int segmentPosition = segmentsStart + (segmentSize * childIndex) + segmentCenterOffset;
        addGraphicsItems(child, row + 1, segmentPosition);
        ++childIndex;
    }

    // add lines from parents to kids
    int boxBottom = yPos + m_treeItemHeight;
    int boxMiddleX = xPos + m_treeItemWidth / 2;
    int yBottomMiddle = boxBottom + m_treeItemVerticalPadding / 2;
    int boxTop = yPos;
    int yTopMiddle = boxTop - m_treeItemVerticalPadding / 2;

    if (row > 0) {
        QGraphicsLineItem *childVerticalStem = new QGraphicsLineItem();
        childVerticalStem->setLine(boxMiddleX, yTopMiddle, boxMiddleX, boxTop);
        m_treeScene->addItem(childVerticalStem);
    }

    if (childCount > 0) {
        QGraphicsLineItem *parentVerticalStem = new QGraphicsLineItem();
        parentVerticalStem->setLine(boxMiddleX, boxBottom, boxMiddleX, yBottomMiddle);
        m_treeScene->addItem(parentVerticalStem);
    }

    if (childCount > 1) {
        QGraphicsLineItem *horizontalStem = new QGraphicsLineItem();
        // match the end points with the horizontal lines
        int lineStartX = segmentsStart + segmentCenterOffset + m_treeItemWidth / 2;
        int lineStopX = segmentsStart + segmentSize * (childCount -1) + segmentCenterOffset + m_treeItemWidth / 2;
        horizontalStem->setLine(lineStartX, yBottomMiddle, lineStopX , yBottomMiddle);
        m_treeScene->addItem(horizontalStem);
    }
}

bool AccessibilitySceneManager::isHidden(QAccessibleInterface *interface)
{
    QAccessibleInterface *current = interface;
    while (current) {

        if (current->state().invisible) {
            return true;
        }

        current = current->parent();
    }

    return false;
}
