/* This file is part of the KDE project
   SPDX-FileCopyrightText: 2001 Andrea Rizzi <rizzi@kde.org>
                      Ulrich Kuettler <ulrich.kuettler@mailbox.tu-dresden.de>
                 2006 Martin Pfeiffer <hubipete@gmx.net>
                 2009 Jeremias Epperlein <jeeree@web.de>

   SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "RowElement.h"

#include "FormulaCursor.h"
#include "FormulaDebug.h"

#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>

#include <QPainter>

RowElement::RowElement(BasicElement *parent)
    : BasicElement(parent)
{
}

RowElement::~RowElement()
{
    qDeleteAll(m_childElements);
}

void RowElement::paint(QPainter &painter, AttributeManager *am)
{
    /* RowElement has no visual representation so paint nothing */
    Q_UNUSED(painter)
    Q_UNUSED(am)
}

void RowElement::paintEditingHints(QPainter &painter, AttributeManager *am)
{
    Q_UNUSED(am)
    if (childElements().count() == 0) {
        painter.save();
        QBrush brush(Qt::NoBrush);
        brush.setColor(Qt::transparent);
        painter.setBrush(brush);
        painter.setPen(QPen(Qt::blue, 0));
        painter.drawRect(childrenBoundingRect());
        painter.restore();
    }
}

void RowElement::layout(const AttributeManager *am)
{
    Q_UNUSED(am) // there are no attributes that can be processed here

    if (m_childElements.isEmpty()) {
        // set standard values for empty formulas
        setOrigin(QPointF(0.0, 0.0));
        setWidth(7.0); // standard values
        setHeight(10.0);
        setBaseLine(10.0);
        setChildrenBoundingRect(QRectF(0, 0, width(), height()));
        return;
    }

    qreal width = 0.0;
    qreal topToBaseline = 0.0;
    qreal baselineToBottom = 0.0;
    foreach (BasicElement *child, m_childElements) // iterate through the children and
        topToBaseline = qMax(topToBaseline, child->baseLine()); // find max baseline

    foreach (BasicElement *child, m_childElements) // iterate through the children
    {
        child->setOrigin(QPointF(width, topToBaseline - child->baseLine()));
        baselineToBottom = qMax(baselineToBottom, child->height() - child->baseLine());
        width += child->width(); // add their width
    }

    setWidth(width);
    setHeight(topToBaseline + baselineToBottom);
    setBaseLine(topToBaseline);
    setChildrenBoundingRect(QRectF(0, 0, width, height()));
}

void RowElement::stretch()
{
    // The elements can grow vertically, so make sure we reposition their vertical
    // origin appropriately
    /*    foreach( BasicElement* tmpElement, childElements() ) {
            tmpElement->stretch();
            //Set the origin.  Note that we ignore the baseline and center the object
            //vertically
            //I think we need to FIXME for symmetric situations or something?
            tmpElement->setOrigin( QPointF(tmpElement->origin().x(), childrenBoundingRect().y() + (childrenBoundingRect().height() - tmpElement->height())/2 ));
        }*/
}

int RowElement::endPosition() const
{
    return m_childElements.count();
}

const QList<BasicElement *> RowElement::childElements() const
{
    return m_childElements;
}

bool RowElement::insertChild(int position, BasicElement *child)
{
    if (0 <= position && position <= endPosition()) {
        m_childElements.insert(position, child);
        child->setParentElement(this);
        return true;
    } else {
        return false;
    }
}

bool RowElement::removeChild(BasicElement *child)
{
    bool tmp = m_childElements.removeOne(child);
    if (tmp) {
        child->setParentElement(nullptr);
    }
    return tmp;
}

bool RowElement::acceptCursor(const FormulaCursor &cursor)
{
    Q_UNUSED(cursor)
    return true;
}

bool RowElement::moveCursor(FormulaCursor &newcursor, FormulaCursor &oldcursor)
{
    Q_UNUSED(oldcursor)
    if ((newcursor.direction() == MoveUp) || (newcursor.direction() == MoveDown) || (newcursor.isHome() && newcursor.direction() == MoveLeft)
        || (newcursor.isEnd() && newcursor.direction() == MoveRight)) {
        // the newcursor can't be moved vertically
        // TODO: check what happens with linebreaks in <mspace> elements
        return false;
    }
    if (newcursor.isSelecting()) {
        switch (newcursor.direction()) {
        case MoveLeft:
            newcursor += -1;
            break;
        case MoveRight:
            newcursor += 1;
            break;
        default:
            break;
        }
    } else {
        switch (newcursor.direction()) {
        case MoveLeft:
            newcursor.setCurrentElement(m_childElements[newcursor.position() - 1]);
            newcursor.moveEnd();
            break;
        case MoveRight:
            newcursor.setCurrentElement(m_childElements[newcursor.position()]);
            newcursor.moveHome();
            break;
        default:
            break;
        }
    }
    return true;
}

QLineF RowElement::cursorLine(int position) const
{
    QPointF top = absoluteBoundingRect().topLeft();
    if (childElements().isEmpty()) {
        // center cursor in elements that have no children
        top += QPointF(width() / 2, 0);
    } else {
        if (position == endPosition()) {
            top += QPointF(width(), 0.0);
        } else {
            top += QPointF(childElements()[position]->boundingRect().left(), 0.0);
        }
    }
    QPointF bottom = top + QPointF(0.0, height());
    return QLineF(top, bottom);
}

bool RowElement::setCursorTo(FormulaCursor &cursor, QPointF point)
{
    if (m_childElements.isEmpty() || point.x() < m_childElements[0]->origin().x()) {
        cursor.moveTo(this, 0);
        return true;
    }
    int i;
    for (i = 0; i < m_childElements.count(); i++) {
        // Find the child element the point is in
        if (m_childElements[i]->boundingRect().right() >= point.x()) {
            break;
        }
    }
    // check if the point is behind all child elements
    if (i == m_childElements.count()) {
        cursor.moveTo(this, endPosition());
        return true;
    } else {
        if (cursor.isSelecting()) {
            // we don't need to change current element because we are already in this element
            if (cursor.mark() <= i) {
                cursor.setPosition(i + 1);
            } else {
                cursor.setPosition(i);
            }
            return true;
        } else {
            return m_childElements[i]->setCursorTo(cursor, point - m_childElements[i]->origin());
        }
    }
}

ElementType RowElement::elementType() const
{
    return Row;
}

bool RowElement::readMathMLContent(const KoXmlElement &parent)
{
    KoXmlElement realParent = parent;

    // Go deeper in the xml tree and 'skip' the semantics elements.
    while (!realParent.namedItemNS(KoXmlNS::math, "semantics").isNull()) { // while there is a child 'semantics'
        realParent = realParent.namedItemNS(KoXmlNS::math, "semantics").toElement(); // move to it
    }

    // Read the actual content.
    BasicElement *tmpElement = nullptr;
    KoXmlElement tmp;
    forEachElement(tmp, realParent)
    {
        tmpElement = ElementFactory::createElement(tmp.tagName(), this);
        Q_ASSERT(tmpElement);
        if (!tmpElement->readMathML(tmp)) {
            return false;
        }

#if 1 // compact empty mrows and mrows with only one element
      // Treat rows in rows specially.
        if (tmpElement->elementType() == Row) {
            if (tmpElement->childElements().count() == 0) {
                // We don't load in this case, empty elements in rows are not needed.
            } else if (tmpElement->childElements().count() == 1) {
                // An mrow with 1 child is equivalent to the child itself.
                // So dig it out and place it directly in this row.
                //
                // TODO: Investigate, if we should load them nevertheless.
                RowElement *row = static_cast<RowElement *>(tmpElement);
                BasicElement *child = row->childElements()[0];
                row->removeChild(child);
                delete row;

                // insertChild(childElements().count(), child);
                m_childElements << child;
            } else {
                // If the mrow has > 1 child, then enter it
                m_childElements << tmpElement;
            }
        } else {
            // All other elements than mrow are immediately entered.
            m_childElements << tmpElement;
        }
#else
        m_childElements << tmpElement;
#endif
    }
    return true;
}

int RowElement::positionOfChild(BasicElement *child) const
{
    return m_childElements.indexOf(child);
}

void RowElement::writeMathMLContent(KoXmlWriter *writer, const QString &ns) const
{
    foreach (BasicElement *tmp, m_childElements)
        tmp->writeMathML(writer, ns);
}

BasicElement *RowElement::elementAfter(int position) const
{
    if (position < endPosition()) {
        return m_childElements[position];
    } else {
        return nullptr;
    }
}

BasicElement *RowElement::elementBefore(int position) const
{
    if (position > 1) {
        return m_childElements[position - 1];
    } else {
        return nullptr;
    }
}

QList<BasicElement *> RowElement::elementsBetween(int pos1, int pos2) const
{
    return m_childElements.mid(pos1, pos2 - pos1);
}

bool RowElement::replaceChild(BasicElement *oldelement, BasicElement *newelement)
{
    int oldElementIndex = m_childElements.indexOf(oldelement);
    if (oldElementIndex < 0)
        return false;

    m_childElements.replace(oldElementIndex, newelement);
    oldelement->setParentElement(nullptr);
    newelement->setParentElement(this);

    return true;
}

bool RowElement::isEmpty() const
{
    return (elementType() == Row && m_childElements.count() == 0);
}

bool RowElement::isInferredRow() const
{
    return true;
}
