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

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

#ifndef BASICELEMENT_H
#define BASICELEMENT_H

#include "ElementFactory.h"
#include "koformula_export.h"

#include <QHash>
#include <QLineF>
#include <QList>
#include <QRectF>
#include <QString>
class QPainter;
class QVariant;
class KoXmlWriter;
#include "KoXmlReaderForward.h"
class AttributeManager;
class FormulaCursor;
class QPainterPath;
class TableDataElement;

/**
 * @short The base class for all elements of a formula
 *
 * The BasicElement class is constructed with a parent and normally an element in a
 * formula has a parent. The only exception is FormulaElement which is the root of
 * the element tree and has no parent element.
 * Most of the elements have children but the number of it can be fixed or variable
 * and the type of child element is not certain. So with the childElements() method you
 * can obtain a list of all direct children of an element. Note that the returned list
 * can be empty when the element is eg a token. This is also the reason why each class
 * inheriting BasicElement has to implement the childElements() method on its own.
 * With the childElementAt method you can test if the given point is in the element.
 * This method is generically implemented for all element types only once in
 * BasicElement.
 * The BasicElement knows its size and position in the formula. This data is normally
 * only used for drawing and stored in the m_boundingRect attribute.
 * To adapt both variables, size and coordinates, to fit in the formula each and every
 * BasicElement derived class has to implement layoutElement().
 *
 * For cursor movement, an element has to implement elementBefore, elementAfter,
 * lastCursorPosition and positionOfChild as well as
 * moveCursor (keyboard navigation) and setCursorTo (cursor placement by clicking).
 */
class KOFORMULA_EXPORT BasicElement
{
public:
    /*
     * The standard constructor
     * @param parent pointer to the BasicElement's parent
     */
    explicit BasicElement(BasicElement *parent = nullptr);

    /// The standard destructor
    virtual ~BasicElement();

    /**
     * Get the element of the formula at the given point
     * @param p the point to look for
     * @return a pointer to a BasicElement
     */
    BasicElement *childElementAt(const QPointF &p);

    /**
     * Obtain a list of all child elements of this element - sorted in saving order
     * @return a QList with pointers to all child elements
     */
    virtual const QList<BasicElement *> childElements() const;

    /**
     * Replace a child element
     * @param oldelement the child to replace
     * @param newelement the child @p oldelement is replaced with
     */
    virtual bool replaceChild(BasicElement *oldelement, BasicElement *newelement);

    /**
     * Render the element to the given QPainter
     * @param painter The QPainter to paint the element to
     * @param am AttributeManager containing style info
     */
    virtual void paint(QPainter &painter, AttributeManager *am);

    /**
     * Render the editing hints of the element to the given QPainter
     * @param painter The QPainter to paint the element to
     * @param am AttributeManager containing style info
     */
    virtual void paintEditingHints(QPainter &painter, AttributeManager *am);

    /**
     * Calculate the minimum size of the element and the positions of its children
     *
     * Laying out the items is done in two parts.
     *
     * First layout() is called for the topmost element, which in turn calls
     * layout() for its children, and so on. This sets the minimum size of all elements.
     *
     * Then stretch() is called for the topmost element, which in turn calls
     * stretch() for its children, and so on.  This stretches elements that
     * are stretchable, up to their maximum size.
     *
     * @param am The AttributeManager providing information about attributes values
     */
    virtual void layout(const AttributeManager *am);

    /**
     * Calculate the stretched size of the element.  This is called after layouting.
     */
    virtual void stretch();

    /**
     * Implement the cursor behaviour for the element
     * @param cursor the cursor we test
     * @return true, if the element accepts the cursor
     */
    virtual bool acceptCursor(const FormulaCursor &cursor);

    /**
     * Return the coordinates of the line, where the cursor should be drawn
     * in coordinates relative to the formula element (or the flake shape)
     * @param position The position of FormulaCursor
     * @return the cursor line
     */
    virtual QLineF cursorLine(int position) const;

    virtual QPainterPath selectionRegion(const int pos1, const int pos2) const;

    virtual QList<BasicElement *> elementsBetween(int pos1, int pos2) const;

    /**
     * Move the cursor in the direction specified in cursor
     * @param newcursor the cursor we move around
     * @param oldcursor the former cursor position
     * @return true, if we moved the cursor
     */
    virtual bool moveCursor(FormulaCursor &newcursor, FormulaCursor &oldcursor);

    /// @return The element's ElementType
    virtual ElementType elementType() const;

    /// Set the element's width to @p width
    void setWidth(qreal width);

    /// @return The width of the element
    qreal width() const;

    /// Set the element's height to @p height
    void setHeight(qreal height);

    /// @return The height of the element
    qreal height() const;

    /// @return The bounding rectangle of the element
    const QRectF &boundingRect() const;

    /// @return The absolute bounding rectangle of the element
    const QRectF absoluteBoundingRect() const;

    /**
     * place the cursor at the given point
     * the point should be placed a the position in the element
     * (or it's child) that is closest to the point
     * in particular the point doesn't have to be within
     * boundingBox()
     * @param cursor The FormulaCursor to modify
     * @param point The point in coordinates relative to the elements local coordinate system
     * @return true, iff the cursor could be placed
     **/
    virtual bool setCursorTo(FormulaCursor &cursor, QPointF point);

    /// @return The bounding rectangle of the children, relative to the element
    const QRectF &childrenBoundingRect() const;

    /// Set the bounding rectangle of the children, relative to the element
    void setChildrenBoundingRect(const QRectF &rect);

    /// Set the element's baseline to @p baseLine
    void setBaseLine(qreal baseLine);

    /// @return The baseline of the element
    qreal baseLine() const;

    /// Set the element's origin inside the m_parentElement to @p origin
    void setOrigin(QPointF origin);

    /// @return The element's origin
    QPointF origin() const;

    /// Set the element's m_parentElement to @p parent
    void setParentElement(BasicElement *parent);

    /// @return The parent element of this BasicElement
    BasicElement *parentElement() const;

    /// @return The last cursor position (number of available cursor positions - 1)
    virtual int endPosition() const;

    /**
     * @return the cursor position before the child in this element and -1 if it isn't a child
     * @param child  the childelement we are looking for
     */
    virtual int positionOfChild(BasicElement *child) const;

    /// @return the element right before the cursor position @p position and 0 if there is none
    virtual BasicElement *elementBefore(int position) const;

    /// @return the element right after the cursor position @p position and 0 if there is none
    virtual BasicElement *elementAfter(int position) const;

    /// Set the element's m_scaleFactor to @p scaleFactor
    void setScaleFactor(qreal scaleFactor);

    /// @return The elements scale factor
    qreal scaleFactor() const;

    /// Set the elements scale level and sets the scale factor
    void setScaleLevel(int scaleLevel);

    /// @return The elements scale level
    int scaleLevel() const;

    /**
     * Set an attribute's value
     * @param name The name of the attribute to be set
     * @param value The value to set for the attribute
     */
    void setAttribute(const QString &name, const QVariant &value);

    /// @return The value of the attribute if it is set for this element
    QString attribute(const QString &attribute) const;

    /// @return The value of the attribute if it is inherited
    virtual QString inheritsAttribute(const QString &attribute) const;

    /// @return The default value of the attribute for this element
    virtual QString attributesDefaultValue(const QString &attribute) const;

    /// Whether displaystyle is set
    bool displayStyle() const;

    /// Whether displaystyle is set.  This is updated by FormulaRenderer
    void setDisplayStyle(bool displayStyle);

    /// Read the element from MathML
    bool readMathML(const KoXmlElement &element);

    /// Save the element to MathML
    void writeMathML(KoXmlWriter *writer, const QString &ns = "math") const;

    /// @return true, if @p other is a descendant of this element
    bool hasDescendant(BasicElement *other) const;

    /// @return first empty element, that is a descendant of this element, if there is one
    BasicElement *emptyDescendant();

    /// @return true, when the element is empty
    virtual bool isEmpty() const;

    /// @return true, if the element is an inferred mrow
    virtual bool isInferredRow() const;

    /// @return the formula element that is a descendant of this element
    BasicElement *formulaElement();

    /** writes the child element tree to qDebug()
     *  only for debugging purpose
     *  @param wrong indicates, if the parent is set wrong
     *  @param indent indentation level
     */
    virtual void writeElementTree(int indent = 0, bool wrong = false) const;

    /// return the content of the element to qDebug(), only for debugging
    virtual const QString writeElementContent() const;

    /// @return the first TableDataElement among the elements ancestors or 0 if there is none
    TableDataElement *parentTableData();

protected:
    /// Read all attributes loaded and add them to the m_attributes map
    virtual bool readMathMLAttributes(const KoXmlElement &element);

    /// Read all content from the node - reimplemented by child elements
    virtual bool readMathMLContent(const KoXmlElement &element);

    /// Write all attributes of m_attributes to @p writer
    virtual void writeMathMLAttributes(KoXmlWriter *writer) const;

    /// Write all content to the KoXmlWriter - reimplemented by the child elements
    virtual void writeMathMLContent(KoXmlWriter *writer, const QString &ns) const;

    static void cleanElementTree(BasicElement *element);

private:
    /// The element's parent element - might not be null except of FormulaElement
    BasicElement *m_parentElement;

    /// A hash map of all attributes where attribute name is assigned to a value
    QHash<QString, QString> m_attributes;

    /// The boundingRect storing the element's width, height, x and y
    QRectF m_boundingRect;

    /** The boundingRect storing the child element's width, height, x and y
     *  The bottomRight hand corner will always be small that then size of
     *  m_boundingRect
     */
    QRectF m_childrenBoundingRect;

    /// The position of our base line from the upper border
    qreal m_baseLine;

    /// Factor with which this element is scaled down by
    qreal m_scaleFactor;

    /// Scale level with which this element is scaled down by
    qreal m_scaleLevel;

    /// Indicates whether this element has displaystyle set
    bool m_displayStyle;
};

#endif // BASICELEMENT_H
