// xlsxcellformula.cpp

#include "xlsxcellformula.h"

#include "xlsxcellformula_p.h"
#include "xlsxutility_p.h"

#include <QDebug>
#include <QObject>
#include <QString>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QtGlobal>

QT_BEGIN_NAMESPACE_XLSX

CellFormulaPrivate::CellFormulaPrivate(const QString &formula_,
                                       const CellRange &ref_,
                                       CellFormula::FormulaType type_)
    : formula(formula_)
    , type(type_)
    , reference(ref_)
    , ca(false)
    , si(0)
{
    // Remove the formula '=' sign if exists
    if (formula.startsWith(QLatin1String("=")))
        formula.remove(0, 1);
    else if (formula.startsWith(QLatin1String("{=")) && formula.endsWith(QLatin1String("}")))
        formula = formula.mid(2, formula.length() - 3);
}

CellFormulaPrivate::CellFormulaPrivate(const CellFormulaPrivate &other)
    : QSharedData(other)
    , formula(other.formula)
    , type(other.type)
    , reference(other.reference)
    , ca(other.ca)
    , si(other.si)
{
}

CellFormulaPrivate::~CellFormulaPrivate()
{
}

/*!
  \class CellFormula
  \inmodule QtXlsx
  \brief The CellFormula class provides a API that is used to handle the cell formula.

*/

/*!
  \enum CellFormula::FormulaType
  \value NormalType
  \value ArrayType
  \value DataTableType
  \value SharedType
*/

/*!
 *  Creates a new formula.
 */
CellFormula::CellFormula()
{
    // The d pointer is initialized with a null pointer
}

/*!
 *  Creates a new formula with the given \a formula and \a type.
 */
CellFormula::CellFormula(const char *formula, FormulaType type)
    : d(new CellFormulaPrivate(QString::fromLatin1(formula), CellRange(), type))
{
}

/*!
 *  Creates a new formula with the given \a formula and \a type.
 */
CellFormula::CellFormula(const QString &formula, FormulaType type)
    : d(new CellFormulaPrivate(formula, CellRange(), type))
{
}

/*!
 *  Creates a new formula with the given \a formula, \a ref and \a type.
 */
CellFormula::CellFormula(const QString &formula, const CellRange &ref, FormulaType type)
    : d(new CellFormulaPrivate(formula, ref, type))
{
}

/*!
   Creates a new formula with the same attributes as the \a other formula.
 */
CellFormula::CellFormula(const CellFormula &other)
    : d(other.d)
{
}

/*!
   Assigns the \a other formula to this formula, and returns a
   reference to this formula.
 */
CellFormula &CellFormula::operator=(const CellFormula &other)
{
    d = other.d;
    return *this;
}

/*!
 * Destroys this formula.
 */
CellFormula::~CellFormula()
{
}

/*!
 * Returns the type of the formula.
 */
CellFormula::FormulaType CellFormula::formulaType() const
{
    return d ? d->type : NormalType;
}

/*!
 * Returns the contents of the formula.
 */
QString CellFormula::formulaText() const
{
    return d ? d->formula : QString();
}

/*!
 * Returns the reference cells of the formula. For normal formula,
 * this will return an invalid CellRange object.
 */
CellRange CellFormula::reference() const
{
    return d ? d->reference : CellRange();
}

/*!
 * Returns whether the formula is valid.
 */
bool CellFormula::isValid() const
{
    return d;
}

/*!
 * Returns the shared index for shared formula.
 */
int CellFormula::sharedIndex() const
{
    return d && d->type == SharedType ? d->si : (-1);
}

/* aca (Always Calculate Array) // not-implemented attribute
 *
 * Only applies to array formulas.
 *
 * true indicates that the entire array shall be calculated in full.
 * If false the individual cells of the array shall be calculated as needed.
 *
 * The aca value shall be ignored unless the value of the corresponding
 *  t attribute is array.
 *
 *  [Note: The primary case where an array formula must be calculated in
 * part instead of in full is when some cells in the array depend on other
 * cells that are semi-calculated, e.g., contains the function =(). end note]
 *
 *  The possible values for this attribute are defined by the W3C XML Schema
 *  boolean datatype.
 */

/* bx (Assigns Value to Name) // not-implemented attribute
 *
 * Specifies that this formula assigns a value to a name.
 *
 * The possible values for this attribute are defined by the W3C XML
 * Schema boolean datatype.
 */

/* del1 (Input 1 Deleted) // not-implemented attribute
 *
 * Whether the first input cell for data table has been deleted.
 * Applies to data table formula only. Written on master cell of data table
 * formula only.
 *
 * The possible values for this attribute are defined by the W3C XML Schema
 * boolean datatype.
 */

/* del2 (Input 2 Deleted) // not-implemented attribute
 *
 * Whether the second input cell for data table has been deleted.
 * Applies to data table formula only. Written on master cell of data
 * table formula only.
 *
 * The possible values for this attribute are defined by the W3C XML Schema
 * boolean datatype.
 */

/* dt2D (Data Table 2-D) // not-implemented attribute
 *
 * Data table is two-dimensional. Only applies to the data tables function.
 * Written on master cell of data table formula only.
 *
 * The possible values for this attribute are defined by the W3C XML Schema
 * boolean datatype.
 */

/* dtr (Data Table Row) // not-implemented attribute
 *
 * true if one-dimensional data table is a row, otherwise it's a column.
 * Only applies to the data tables function. Written on master cell of data
 * table formula only.
 *
 * The possible values for this attribute are defined by the W3C XML Schema
 *  boolean datatype.
 */

/* r1 (Data Table Cell 1) // not-implemented attribute
 *
 * First input cell for data table. Only applies to the data tables array
 * function "TABLE()". Written on master cell of data table formula only.
 *
 * The possible values for this attribute are defined by the ST_CellRef
 * simple type (§18.18.7).
 */

/* r2 (Input Cell 2) // not-implemented attribute
 *
 * Second input cell for data table when dt2D is '1'. Only applies to the
 * data tables array function "TABLE()".Written on master cell of data table
 * formula only.
 *
 * The possible values for this attribute are defined by the ST_CellRef
 * simple type (§18.18.7).
 */

/*!
 * \internal
 * \remark pair with loadFromXml()
 */
bool CellFormula::saveToXml(QXmlStreamWriter &writer) const
{

    // t (Formula Type)
    //
    // Type of formula.
    // The possible values for this attribute are defined by the
    // ST_CellFormulaType simple type (§18.18.6).
    //
    // 18.18.6 ST_CellFormulaType (Formula Type)
    // array (Array Formula)
    // dataTable (Table Formula)
    // normal (Normal)
    // shared (Shared Formula)

    QString t;
    switch (d->type) {
    case CellFormula::ArrayType:
        t = QStringLiteral("array");
        break;
    case CellFormula::SharedType:
        t = QStringLiteral("shared");
        break;
    case CellFormula::NormalType:
        t = QStringLiteral("normal");
        break;
    case CellFormula::DataTableType:
        t = QStringLiteral("dataTable");
        break;
    default: // undefined type
        return false;
        break;
    }

    // f (Formula)
    //
    // Formula for the cell. The formula expression is contained in the
    // character node of this element.
    writer.writeStartElement(QStringLiteral("f"));

    if (!t.isEmpty()) {
        writer.writeAttribute(QStringLiteral("t"), t); // write type(t)
    }

    // ref (Range of Cells)
    //
    // Range of cells which the formula applies to.
    // Only required for shared formula, array formula or data table.
    // Only written on the master formula,
    // not subsequent formulas belonging to the same shared group, array,
    // or data table.
    // The possible values for this attribute are defined by the ST_Ref
    // simple type (§18.18.62).

    if (d->type == CellFormula::SharedType || d->type == CellFormula::ArrayType ||
        d->type == CellFormula::DataTableType) {
        if (d->reference.isValid()) {
            writer.writeAttribute(QStringLiteral("ref"), d->reference.toString());
        }
    }

    // ca (Calculate Cell)
    //
    // Indicates that this formula needs to be recalculated the next time
    // calculation is performed. [Example: This is always set on volatile
    // functions, like =(), and circular references. end example]
    // The possible values for this attribute are defined by the W3C XML
    // Schema boolean datatype.
    //
    // 3.2.2 boolean
    // 3.2.2.1 Lexical representation
    // An instance of a datatype that is defined as ·boolean· can have the
    // following legal literals {true, false, 1, 0}.

    if (d->ca) {
        writer.writeAttribute(QStringLiteral("ca"), QStringLiteral("1"));
    }

    // si (Shared Group Index)
    // Optional attribute to optimize load performance by sharing formulas.
    //
    // When a formula is a shared formula (t value is shared) then this value
    // indicates the group to which this particular cell's formula belongs. The
    // first formula in a group of shared formulas is saved in the f element.
    // This is considered the 'master' formula cell. Subsequent cells sharing
    // this formula need not have the formula written in their f element.
    // Instead, the attribute si value for a particular cell is used to figure
    // what the formula expression should be based on the cell's relative
    // location to the master formula cell.

    if (d->type == CellFormula::SharedType) {
        int si = d->si;
        writer.writeAttribute(QStringLiteral("si"), QString::number(si));
    }

    if (!d->formula.isEmpty()) {
        QString strFormula = d->formula;
        writer.writeCharacters(strFormula); // write formula
    }

    writer.writeEndElement(); // f

    return true;
}

/*!
 * \internal
 * \remark pair with saveToXml()
 */
bool CellFormula::loadFromXml(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("f"));
    if (!d)
        d = new CellFormulaPrivate(QString(), CellRange(), NormalType);

    QXmlStreamAttributes attributes = reader.attributes();
    QString typeString              = attributes.value(QLatin1String("t")).toString();

    // branch: shared-formula
    //
    if (typeString == QLatin1String("array")) {
        d->type = ArrayType;
    } else if (typeString == QLatin1String("shared")) {
        d->type = SharedType;
    } else if (typeString == QLatin1String("normal")) {
        d->type = NormalType;
    } else if (typeString == QLatin1String("dataTable")) {
        d->type = DataTableType;
    } else {
        /*
        // undefined type
        // qDebug() << "Undefined type" << typeString;
        return false;
        // */

        // dev40 {{
        // https://github.com/QtExcel/QXlsx/issues/38
        d->type = NormalType; // Change: normal Type is not mentioned in the xml file!!!!!
        // }}
    }

    // branch: shared-formula
    //
    // ref (Range of Cells)
    // Range of cells which the formula applies to.
    // Only required for shared formula, array formula or data table.
    if (d->type == CellFormula::SharedType || d->type == CellFormula::ArrayType ||
        d->type == CellFormula::DataTableType) {
        if (attributes.hasAttribute(QLatin1String("ref"))) {
            QString refString = attributes.value(QLatin1String("ref")).toString();
            d->reference      = CellRange(refString);
        }
    }

    // branch: shared-formula
    //
    // si (Shared Group Index)
    // Optional attribute to optimize load performance by sharing formulas.
    // When a formula is a shared formula (t value is shared) then this value
    // indicates the group to which this particular cell's formula belongs.
    if (d->type == CellFormula::SharedType) {
        QString ca = attributes.value(QLatin1String("si")).toString();
        d->ca      = parseXsdBoolean(ca, false);

        if (attributes.hasAttribute(QLatin1String("si"))) {
            d->si = attributes.value(QLatin1String("si")).toInt();
        }
    }

    d->formula = reader.readElementText(); // read formula

    return true;
}

/*!
 * \internal
 */
bool CellFormula::operator==(const CellFormula &formula) const
{
    return d->formula == formula.d->formula && d->type == formula.d->type && d->si == formula.d->si;
}

/*!
 * \internal
 */
bool CellFormula::operator!=(const CellFormula &formula) const
{
    return d->formula != formula.d->formula || d->type != formula.d->type || d->si != formula.d->si;
}

QT_END_NAMESPACE_XLSX
