/* 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 "FractionElement.h"

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

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

#include <QPainter>

FractionElement::FractionElement(BasicElement *parent)
    : FixedElement(parent)
{
    m_numerator = new RowElement(this);
    m_denominator = new RowElement(this);
    m_lineThickness = 1.0;
}

FractionElement::~FractionElement()
{
    delete m_numerator;
    delete m_denominator;
}

void FractionElement::paint(QPainter &painter, AttributeManager *am)
{
    Q_UNUSED(am)
    // return if there is nothing to paint
    if (m_lineThickness == 0.0)
        return;

    // paint the fraction line with the specified line width
    QPen pen;
    pen.setWidthF(m_lineThickness);
    painter.setPen(pen);
    painter.drawLine(m_fractionLine);
}

void FractionElement::layout(const AttributeManager *am)
{
    // get values of all attributes
    QString value = am->findValue("linethickness", this);
    Length length;
    if (value == "thick")
        length.value = 2;
    else if (value == "medium")
        length.value = 1;
    else if (value == "thin")
        length.value = 0.5;
    else
        length = am->parseUnit(value, this);

    if (length.unit == Length::None)
        m_lineThickness = am->lineThickness(this) * length.value;
    else
        m_lineThickness = am->lengthToPixels(length, this, "linethickness");

    // decide which layout is wanted
    if (am->boolOf("bevelled", this)) {
        layoutBevelledFraction(am);
        return;
    }

    qreal distY = am->layoutSpacing(this);
    Align numalign = am->alignOf("numalign", this);
    Align denomalign = am->alignOf("denomalign", this);

    // align the numerator and the denominator
    QPointF numeratorOrigin;
    QPointF denominatorOrigin(0.0, m_numerator->height() + m_lineThickness + 2 * distY);
    setWidth(qMax(m_numerator->width(), m_denominator->width()) + m_lineThickness * 2);

    if (numalign == Right)
        numeratorOrigin.setX(width() - m_numerator->width() - m_lineThickness);
    else if (numalign == Center)
        numeratorOrigin.setX((width() - m_numerator->width()) / 2);

    if (denomalign == Right)
        denominatorOrigin.setX(width() - m_denominator->width() - m_lineThickness);
    else if (numalign == Center)
        denominatorOrigin.setX((width() - m_denominator->width()) / 2);

    m_numerator->setOrigin(numeratorOrigin);
    m_denominator->setOrigin(denominatorOrigin);

    // construct the fraction's line
    qreal fractionLineY = m_numerator->height() + m_lineThickness / 2 + distY;
    m_fractionLine = QLineF(QPointF(m_lineThickness, fractionLineY), QPointF(width() - m_lineThickness, fractionLineY));

    setHeight(m_numerator->height() + m_denominator->height() + m_lineThickness + 2 * distY);
    setBaseLine(denominatorOrigin.y());
}

void FractionElement::layoutBevelledFraction(const AttributeManager *am)
{
    // the shown line should have a width that has 1/3 of the height
    // the line is higher as the content by 2*thinmathspace = 2*borderY

    qreal borderY = am->layoutSpacing(this);
    setHeight(m_numerator->height() + m_denominator->height() + 2 * borderY);
    setWidth(m_numerator->width() + m_denominator->width() + height() / 3);
    setBaseLine(height() / 2);

    m_numerator->setOrigin(QPointF(0.0, borderY));
    m_denominator->setOrigin(QPointF(width() - m_denominator->width(), borderY + m_numerator->height()));
    m_fractionLine = QLineF(QPointF(m_numerator->width(), height()), QPointF(width() - m_denominator->width(), 0.0));
}

const QList<BasicElement *> FractionElement::childElements() const
{
    QList<BasicElement *> list;
    list << m_numerator << m_denominator;
    return list;
}

int FractionElement::endPosition() const
{
    return 3;
}

// QLineF FractionElement::cursorLine(int position) const {
//     QPointF top=absoluteBoundingRect().topLeft();
//     QPointF bottom;
//     switch (position) {
// 	case 0:
// 	    top+=m_numerator->origin();
// 	    break;
// 	case 1:
// 	    top+=m_numerator->origin()+QPointF(m_numerator->width(),0.0);
// 	    break;
// 	case 2:
// 	    top+=m_denominator->origin();
// 	    break;
// 	case 3:
// 	    top+=m_denominator->origin()+QPointF(m_denominator->width(),0.0);
// 	    break;
//     }
//     if (position<=1) {
// 	bottom=top+QPointF(0.0,m_numerator->height());
//     }
//     else {
// 	bottom=top+QPointF(0.0,m_denominator->height());
//     }
//     return QLineF(top, bottom);
// }

QList<BasicElement *> FractionElement::elementsBetween(int pos1, int pos2) const
{
    QList<BasicElement *> tmp;
    if (pos1 == 0 && pos2 > 0) {
        tmp.append(m_numerator);
    }
    if (pos1 < 3 && pos2 == 3) {
        tmp.append(m_denominator);
    }
    return tmp;
}

int FractionElement::positionOfChild(BasicElement *child) const
{
    if (m_numerator == child) {
        return 0;
    } else if (m_denominator == child) {
        return 2;
    }
    return -1;
}

bool FractionElement::moveCursor(FormulaCursor &newcursor, FormulaCursor &oldcursor)
{
    if (newcursor.isSelecting()) {
        return false;
    } else {
        // TODO: How can I get the attribute of the Attributemanager here?
        //  The movement should be different in the bevelled case
        // if (bevelled) {
        //  return moveHorSituation(newcursor,oldcursor,0,1 )
        return moveVertSituation(newcursor, oldcursor, 0, 1);
    }
}

bool FractionElement::setCursorTo(FormulaCursor &cursor, QPointF point)
{
    // check if the point is above the fraction line, the origin is in the top left corner
    bool inNumerator = point.y() < (m_numerator->boundingRect().bottom() + m_denominator->boundingRect().top()) / 2;
    if (cursor.isSelecting()) {
        return false;
    } else {
        if (point.x() > width()) {
            cursor.moveTo(this, inNumerator ? 1 : 3);
            return true;
        }
        if (point.x() < 0) {
            cursor.moveTo(this, inNumerator ? 0 : 2);
            return true;
        }
        if (inNumerator) {
            point -= m_numerator->origin();
            // TODO: maybe place it directly in the fraction if this fails
            return m_numerator->setCursorTo(cursor, point);
        } else {
            point -= m_denominator->origin();
            return m_denominator->setCursorTo(cursor, point);
        }
    }
}

bool FractionElement::replaceChild(BasicElement *oldelement, BasicElement *newelement)
{
    // TODO: investigate, if we really need this
    if (newelement->elementType() == Row) {
        RowElement *newrow = static_cast<RowElement *>(newelement);
        if (oldelement == m_numerator) {
            m_numerator = newrow;
            return true;
        } else if (oldelement == m_denominator) {
            m_denominator = newrow;
            return true;
        }
    }
    return false;
}

QString FractionElement::attributesDefaultValue(const QString &attribute) const
{
    if (attribute == "linethickness")
        return "1";
    else if (attribute == "numalign" || attribute == "denomalign")
        return "center";
    else if (attribute == "bevelled")
        return "false";
    else
        return QString();
}

bool FractionElement::readMathMLContent(const KoXmlElement &parent)
{
    KoXmlElement tmp;
    int counter = 0;
    forEachElement(tmp, parent)
    {
        if (counter == 0) {
            loadElement(tmp, &m_numerator);
        } else if (counter == 1) {
            loadElement(tmp, &m_denominator);
        } else {
            debugFormula << "Too many arguments to mfrac";
        }
        counter++;
    }
    if (counter < 2) {
        debugFormula << "Not enough arguments to mfrac";
    }
    return true;
}

void FractionElement::writeMathMLContent(KoXmlWriter *writer, const QString &ns) const
{
    m_numerator->writeMathML(writer, ns);
    m_denominator->writeMathML(writer, ns);
}

ElementType FractionElement::elementType() const
{
    return Fraction;
}
