
/* 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>

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

#include "TableElement.h"

#include "AttributeManager.h"
#include "FormulaCursor.h"
#include "FormulaDebug.h"
#include "TableRowElement.h"

#include <KoXmlReader.h>

#include <QList>
#include <QPainter>

TableElement::TableElement(BasicElement *parent)
    : BasicElement(parent)
{
    m_framePenStyle = Qt::NoPen;
}

TableElement::~TableElement() = default;

void TableElement::paint(QPainter &painter, AttributeManager *am)
{
    // draw frame
    // if( m_framePenStyle != Qt::NoPen ) {
    //    painter.setPen( QPen( m_framePenStyle ) );
    // painter.drawRect( QRectF( 0.0, 0.0, width(), height() ) );
    //}
    painter.save();
    QList<qreal> frameSpacing = am->doubleListOf("framespacing", this);
    // QList<qreal> rowSpacing = am->doubleListOf( "rowspacing", this );
    debugFormula << frameSpacing;
    painter.setPen(QPen(Qt::NoPen)); // debugging
    painter.drawRect(QRectF(0.0, 0.0, width(), height()));
    // draw rowlines
    qreal offset = frameSpacing[1];
    for (int i = 0; i < m_rowHeights.count() - 1; i++) {
        offset += m_rowHeights[i];
        painter.drawLine(QPointF(0.0, offset), QPointF(width(), offset));
    }

    // draw columnlines
    offset = frameSpacing[0];
    for (int i = 0; i < m_colWidths.count() - 1; i++) {
        offset += m_colWidths[i];
        painter.drawLine(QPointF(offset, 0.0), QPointF(offset, height()));
    }
    painter.restore();
}

void TableElement::layout(const AttributeManager *am)
{
    // lookup attribute values needed for this table
    m_framePenStyle = am->penStyleOf("frame", this);
    m_rowLinePenStyles = am->penStyleListOf("rowlines", this);
    m_colLinePenStyles = am->penStyleListOf("columnlines", this);
    QList<qreal> frameSpacing = am->doubleListOf("framespacing", this);
    QList<qreal> rowSpacing = am->doubleListOf("rowspacing", this);

    // layout the rows vertically
    qreal tmpX = frameSpacing[0];
    qreal tmpY = frameSpacing[1];
    for (int i = 0; i < m_rows.count(); i++) {
        m_rows[i]->setOrigin(QPointF(tmpX, tmpY));
        tmpY += m_rows[i]->height();
        tmpY += (i < rowSpacing.count()) ? rowSpacing[i] : rowSpacing.last();
    }

    // add the spacing to tmpX and tmpY
    tmpX += m_rows.first()->width();
    tmpX += frameSpacing[0];
    tmpY += frameSpacing[1];
    setWidth(tmpX);
    setHeight(tmpY);
    setBaseLine(height() / 2);
}

ElementType TableElement::elementType() const
{
    return Table;
}

void TableElement::determineDimensions()
{
    AttributeManager am;
    bool equalRows = am.boolOf("equalrows", this);
    bool equalColumns = am.boolOf("equalcolumns", this);
    m_rowHeights.clear();
    m_colWidths.clear();
    // determine the dimensions of each row and col based on the biggest element in it
    BasicElement *entry;
    qreal maxWidth = 0.0;
    qreal maxHeight = 0.0;
    for (int row = 0; row < m_rows.count(); row++) {
        m_rowHeights << 0.0;
        for (int col = 0; col < m_rows.first()->childElements().count(); col++) {
            if (m_colWidths.count() <= col)
                m_colWidths << 0.0;

            entry = m_rows[row]->childElements()[col];
            m_colWidths[col] = qMax(m_colWidths[col], entry->width());
            m_rowHeights[row] = qMax(m_rowHeights[row], entry->height());
            maxWidth = qMax(entry->width(), maxWidth);
        }
        maxHeight = qMax(m_rowHeights[row], maxHeight);
    }

    // treat equalcol and equalrow attributes
    if (equalRows)
        for (int i = 0; i < m_rowHeights.count(); i++)
            m_rowHeights.replace(i, maxHeight);

    if (equalColumns)
        for (int i = 0; i < m_colWidths.count(); i++)
            m_colWidths.replace(i, maxWidth);
}

qreal TableElement::columnWidth(int column)
{
    // if( m_colWidths.isEmpty() )
    determineDimensions();

    return m_colWidths[column];
}

qreal TableElement::rowHeight(TableRowElement *row)
{
    // if( m_rowHeights.isEmpty() )
    determineDimensions();

    return m_rowHeights[m_rows.indexOf(row)];
}

const QList<BasicElement *> TableElement::childElements() const
{
    QList<BasicElement *> tmp;
    foreach (TableRowElement *tmpRow, m_rows)
        tmp << tmpRow;
    return tmp;
}

int TableElement::positionOfChild(BasicElement *child) const
{
    TableRowElement *temp = dynamic_cast<TableRowElement *>(child);
    if (temp == nullptr) {
        return -1;
    } else {
        int p = m_rows.indexOf(temp);
        return (p == -1) ? -1 : 2 * p;
    }
}

QLineF TableElement::cursorLine(int position) const
{
    QPointF top;
    QRectF rect = m_rows[position / 2]->absoluteBoundingRect();
    if (position % 2 == 0) {
        // we are in front of a row
        top = rect.topLeft();
    } else {
        top = rect.topRight();
    }
    QPointF bottom = top + QPointF(0.0, rect.height());
    return QLineF(top, bottom);
}

bool TableElement::setCursorTo(FormulaCursor &cursor, QPointF point)
{
    if (cursor.isSelecting()) {
        return false;
    }
    int i;
    for (i = 0; i < m_rows.count() - 1; i++) {
        if (m_rows[i]->boundingRect().bottom() > point.y()) {
            break;
        }
    }
    point -= m_rows[i]->origin();
    return m_rows[i]->setCursorTo(cursor, point);
}

bool TableElement::moveCursor(FormulaCursor &newcursor, FormulaCursor &oldcursor)
{
    Q_UNUSED(oldcursor)
    int p = newcursor.position();
    switch (newcursor.direction()) {
    case MoveLeft:
        if (p % 2 == 0) {
            // we are in front of a table row
            return false;
        } else {
            if (newcursor.isSelecting()) {
                newcursor.moveTo(this, p - 1);
            } else {
                newcursor.moveTo(m_rows[p / 2], m_rows[p / 2]->endPosition());
            }
            break;
        }
    case MoveRight:
        if (p % 2 == 1) {
            // we are behind a table row
            return false;
        } else {
            if (newcursor.isSelecting()) {
                newcursor.moveTo(this, p + 1);
            } else {
                newcursor.moveTo(m_rows[p / 2], 0);
            }
            break;
        }
    case MoveUp:
        if (p <= 1) {
            return false;
        } else {
            newcursor.moveTo(this, p - 2);
            break;
        }
    case MoveDown:
        if (p < (m_rows.count() - 1) * 2) {
            newcursor.moveTo(this, p + 2);
            break;
        } else {
            return false;
        }
    default:
        break;
    }
    return true;
}

int TableElement::endPosition() const
{
    if (m_rows.count() > 0) {
        return 2 * m_rows.count() - 1;
    } else {
        return 1;
    }
}

bool TableElement::acceptCursor(const FormulaCursor &cursor)
{
    //    return false;
    return cursor.isSelecting();
}

QPainterPath TableElement::selectionRegion(const int pos1, const int pos2) const
{
    int p1 = pos1 % 2 == 0 ? pos1 : pos1 + 1; // pos1 should be before an element
    int p2 = pos2 % 2 == 0 ? pos2 : pos2 + 1; // pos2 should be behind an element
    QPainterPath P;

    for (int i = p1; i < p2; i += 2) {
        P.addRect(m_rows[i / 2]->absoluteBoundingRect());
    }
    return P;
}

QString TableElement::attributesDefaultValue(const QString &attribute) const
{
    if (attribute == "align")
        return "axis";
    else if (attribute == "rowalign")
        return "baseline";
    else if (attribute == "columnalign")
        return "center";
    else if (attribute == "groupalign")
        return "left";
    else if (attribute == "alignmentscope")
        return "true";
    else if (attribute == "columnwidth")
        return "auto";
    else if (attribute == "width")
        return "auto";
    else if (attribute == "rowspacing")
        return "1.0ex";
    else if (attribute == "columnspacing")
        return "0.8em";
    else if (attribute == "rowlines" || attribute == "columnlines" || attribute == "frame")
        return "none";
    else if (attribute == "framespacing")
        return "0.4em 0.5ex";
    else if (attribute == "equalrows" || attribute == "equalcolumns" || attribute == "displaystyle")
        return "false";
    else if (attribute == "side")
        return "right";
    else if (attribute == "minlabelspacing")
        return "0.8em";
    else
        return QString();
}

bool TableElement::readMathMLContent(const KoXmlElement &element)
{
    BasicElement *tmpElement = nullptr;
    KoXmlElement tmp;
    forEachElement(tmp, element) // iterate over the elements
    {
        tmpElement = ElementFactory::createElement(tmp.tagName(), this);
        if (tmpElement->elementType() != TableRow)
            return false;

        m_rows << static_cast<TableRowElement *>(tmpElement);
        tmpElement->readMathML(tmp);
    }

    return true;
}

void TableElement::writeMathMLContent(KoXmlWriter *writer, const QString &ns) const
{
    foreach (TableRowElement *tmpRow, m_rows) { // write each mtr element
        tmpRow->writeMathML(writer, ns);
    }
}

bool TableElement::insertChild(int position, BasicElement *child)
{
    if (child->elementType() == TableRow && !child->childElements().isEmpty() && child->childElements()[0]->elementType() == TableData) {
        TableRowElement *tmp = static_cast<TableRowElement *>(child);
        m_rows.insert(position, tmp);
        tmp->setParentElement(this);
        // TODO: there must be a more efficient way for this
        determineDimensions();

        return true;
    } else {
        return false;
    }
}

bool TableElement::removeChild(BasicElement *child)
{
    if (child->elementType() != TableRow) {
        return false;
    }
    TableRowElement *tmp = static_cast<TableRowElement *>(child);
    if (m_rows.indexOf(tmp) == -1) {
        return false;
    } else {
        m_rows.removeAll(tmp);
        tmp->setParentElement(nullptr);
    }
    return true;
}
