// xlsxconditionalformatting.cpp

#include "xlsxconditionalformatting.h"

#include "xlsxcellrange.h"
#include "xlsxconditionalformatting_p.h"
#include "xlsxstyles_p.h"
#include "xlsxworksheet.h"

#include <QDebug>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

QT_BEGIN_NAMESPACE_XLSX

ConditionalFormattingPrivate::ConditionalFormattingPrivate()
{
}

ConditionalFormattingPrivate::ConditionalFormattingPrivate(
    const ConditionalFormattingPrivate &other)
    : QSharedData(other)
{
}

ConditionalFormattingPrivate::~ConditionalFormattingPrivate()
{
}

void ConditionalFormattingPrivate::writeCfVo(QXmlStreamWriter &writer,
                                             const XlsxCfVoData &cfvo) const
{
    writer.writeEmptyElement(QStringLiteral("cfvo"));
    QString type;
    switch (cfvo.type) {
    case ConditionalFormatting::VOT_Formula:
        type = QStringLiteral("formula");
        break;
    case ConditionalFormatting::VOT_Max:
        type = QStringLiteral("max");
        break;
    case ConditionalFormatting::VOT_Min:
        type = QStringLiteral("min");
        break;
    case ConditionalFormatting::VOT_Num:
        type = QStringLiteral("num");
        break;
    case ConditionalFormatting::VOT_Percent:
        type = QStringLiteral("percent");
        break;
    case ConditionalFormatting::VOT_Percentile:
        type = QStringLiteral("percentile");
        break;
    default:
        break;
    }
    writer.writeAttribute(QStringLiteral("type"), type);
    writer.writeAttribute(QStringLiteral("val"), cfvo.value);
    if (!cfvo.gte)
        writer.writeAttribute(QStringLiteral("gte"), QStringLiteral("0"));
}

/*!
 * \class ConditionalFormatting
 * \brief Conditional formatting for single cell or ranges
 * \inmodule QtXlsx
 *
 * The conditional formatting can be applied to a single cell or ranges of cells.
 */

/*!
    \enum ConditionalFormatting::HighlightRuleType

    \value Highlight_LessThan
    \value Highlight_LessThanOrEqual
    \value Highlight_Equal
    \value Highlight_NotEqual
    \value Highlight_GreaterThanOrEqual
    \value Highlight_GreaterThan
    \value Highlight_Between
    \value Highlight_NotBetween

    \value Highlight_ContainsText
    \value Highlight_NotContainsText
    \value Highlight_BeginsWith
    \value Highlight_EndsWith

    \value Highlight_TimePeriod

    \value Highlight_Duplicate
    \value Highlight_Unique

    \value Highlight_Blanks
    \value Highlight_NoBlanks
    \value Highlight_Errors
    \value Highlight_NoErrors

    \value Highlight_Top
    \value Highlight_TopPercent
    \value Highlight_Bottom
    \value Highlight_BottomPercent

    \value Highlight_AboveAverage
    \value Highlight_AboveOrEqualAverage
    \value Highlight_BelowAverage
    \value Highlight_BelowOrEqualAverage
    \value Highlight_AboveStdDev1
    \value Highlight_AboveStdDev2
    \value Highlight_AboveStdDev3
    \value Highlight_BelowStdDev1
    \value Highlight_BelowStdDev2
    \value Highlight_BelowStdDev3

    \value Highlight_Expression
*/

/*!
    \enum ConditionalFormatting::ValueObjectType

    \value VOT_Formula
    \value VOT_Max
    \value VOT_Min
    \value VOT_Num
    \value VOT_Percent
    \value VOT_Percentile
*/

/*!
    Construct a conditional formatting object
*/
ConditionalFormatting::ConditionalFormatting()
    : d(new ConditionalFormattingPrivate())
{
}

/*!
    Constructs a copy of \a other.
*/
ConditionalFormatting::ConditionalFormatting(const ConditionalFormatting &other)
    : d(other.d)
{
}

/*!
    Assigns \a other to this conditional formatting and returns a reference to
    this conditional formatting.
 */
ConditionalFormatting &ConditionalFormatting::operator=(const ConditionalFormatting &other)
{
    if (this != &other) // Self-assignment check [cert-oop54-cpp]
    {
        this->d = other.d;
    }
    return *this;
}

/*!
 * Destroy the object.
 */
ConditionalFormatting::~ConditionalFormatting()
{
}

/*!
 * Add a highlight rule with the given \a type, \a formula1, \a formula2,
 * \a format and \a stopIfTrue.
 * Return false if failed.
 */
bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type,
                                                  const QString &formula1,
                                                  const QString &formula2,
                                                  const Format &format,
                                                  bool stopIfTrue)
{
    if (format.isEmpty())
        return false;

    bool skipFormula = false;

    auto cfRule = std::make_shared<XlsxCfRuleData>();
    if (type >= Highlight_LessThan && type <= Highlight_NotBetween) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("cellIs");
        QString op;
        switch (type) {
        case Highlight_Between:
            op = QStringLiteral("between");
            break;
        case Highlight_Equal:
            op = QStringLiteral("equal");
            break;
        case Highlight_GreaterThan:
            op = QStringLiteral("greaterThan");
            break;
        case Highlight_GreaterThanOrEqual:
            op = QStringLiteral("greaterThanOrEqual");
            break;
        case Highlight_LessThan:
            op = QStringLiteral("lessThan");
            break;
        case Highlight_LessThanOrEqual:
            op = QStringLiteral("lessThanOrEqual");
            break;
        case Highlight_NotBetween:
            op = QStringLiteral("notBetween");
            break;
        case Highlight_NotEqual:
            op = QStringLiteral("notEqual");
            break;
        default:
            break;
        }
        cfRule->attrs[XlsxCfRuleData::A_operator] = op;
    } else if (type >= Highlight_ContainsText && type <= Highlight_EndsWith) {
        if (type == Highlight_ContainsText) {
            cfRule->attrs[XlsxCfRuleData::A_type]     = QStringLiteral("containsText");
            cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("containsText");
            cfRule->attrs[XlsxCfRuleData::A_formula1_temp] =
                QStringLiteral("NOT(ISERROR(SEARCH(\"%1\",%2)))").arg(formula1);
        } else if (type == Highlight_NotContainsText) {
            cfRule->attrs[XlsxCfRuleData::A_type]     = QStringLiteral("notContainsText");
            cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("notContains");
            cfRule->attrs[XlsxCfRuleData::A_formula1_temp] =
                QStringLiteral("ISERROR(SEARCH(\"%2\",%1))").arg(formula1);
        } else if (type == Highlight_BeginsWith) {
            cfRule->attrs[XlsxCfRuleData::A_type]     = QStringLiteral("beginsWith");
            cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("beginsWith");
            cfRule->attrs[XlsxCfRuleData::A_formula1_temp] =
                QStringLiteral("LEFT(%2,LEN(\"%1\"))=\"%1\"").arg(formula1);
        } else {
            cfRule->attrs[XlsxCfRuleData::A_type]     = QStringLiteral("endsWith");
            cfRule->attrs[XlsxCfRuleData::A_operator] = QStringLiteral("endsWith");
            cfRule->attrs[XlsxCfRuleData::A_formula1_temp] =
                QStringLiteral("RIGHT(%2,LEN(\"%1\"))=\"%1\"").arg(formula1);
        }
        cfRule->attrs[XlsxCfRuleData::A_text] = formula1;
        skipFormula                           = true;
    } else if (type == Highlight_TimePeriod) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("timePeriod");
        //: Todo
        return false;
    } else if (type == Highlight_Duplicate) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("duplicateValues");
    } else if (type == Highlight_Unique) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("uniqueValues");
    } else if (type == Highlight_Errors) {
        cfRule->attrs[XlsxCfRuleData::A_type]          = QStringLiteral("containsErrors");
        cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("ISERROR(%1)");
        skipFormula                                    = true;
    } else if (type == Highlight_NoErrors) {
        cfRule->attrs[XlsxCfRuleData::A_type]          = QStringLiteral("notContainsErrors");
        cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("NOT(ISERROR(%1))");
        skipFormula                                    = true;
    } else if (type == Highlight_Blanks) {
        cfRule->attrs[XlsxCfRuleData::A_type]          = QStringLiteral("containsBlanks");
        cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEN(TRIM(%1))=0");
        skipFormula                                    = true;
    } else if (type == Highlight_NoBlanks) {
        cfRule->attrs[XlsxCfRuleData::A_type]          = QStringLiteral("notContainsBlanks");
        cfRule->attrs[XlsxCfRuleData::A_formula1_temp] = QStringLiteral("LEN(TRIM(%1))>0");
        skipFormula                                    = true;
    } else if (type >= Highlight_Top && type <= Highlight_BottomPercent) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("top10");
        if (type == Highlight_Bottom || type == Highlight_BottomPercent)
            cfRule->attrs[XlsxCfRuleData::A_bottom] = QStringLiteral("1");
        if (type == Highlight_TopPercent || type == Highlight_BottomPercent)
            cfRule->attrs[XlsxCfRuleData::A_percent] = QStringLiteral("1");
        cfRule->attrs[XlsxCfRuleData::A_rank] =
            !formula1.isEmpty() ? formula1 : QStringLiteral("10");
        skipFormula = true;
    } else if (type >= Highlight_AboveAverage && type <= Highlight_BelowStdDev3) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("aboveAverage");
        if (type >= Highlight_BelowAverage && type <= Highlight_BelowStdDev3)
            cfRule->attrs[XlsxCfRuleData::A_aboveAverage] = QStringLiteral("0");
        if (type == Highlight_AboveOrEqualAverage || type == Highlight_BelowOrEqualAverage)
            cfRule->attrs[XlsxCfRuleData::A_equalAverage] = QStringLiteral("1");
        if (type == Highlight_AboveStdDev1 || type == Highlight_BelowStdDev1)
            cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("1");
        else if (type == Highlight_AboveStdDev2 || type == Highlight_BelowStdDev2)
            cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("2");
        else if (type == Highlight_AboveStdDev3 || type == Highlight_BelowStdDev3)
            cfRule->attrs[XlsxCfRuleData::A_stdDev] = QStringLiteral("3");
    } else if (type == Highlight_Expression) {
        cfRule->attrs[XlsxCfRuleData::A_type] = QStringLiteral("expression");
    } else {
        return false;
    }

    cfRule->dxfFormat = format;
    if (stopIfTrue)
        cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true;
    if (!skipFormula) {
        if (!formula1.isEmpty())
            cfRule->attrs[XlsxCfRuleData::A_formula1] =
                formula1.startsWith(QLatin1String("=")) ? formula1.mid(1) : formula1;
        if (!formula2.isEmpty())
            cfRule->attrs[XlsxCfRuleData::A_formula2] =
                formula2.startsWith(QLatin1String("=")) ? formula2.mid(1) : formula2;
    }
    d->cfRules.append(cfRule);
    return true;
}

/*!
 * \overload
 *
 * Add a highlight rule with the given \a type \a format and \a stopIfTrue.
 */
bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type,
                                                  const Format &format,
                                                  bool stopIfTrue)
{
    if ((type >= Highlight_AboveAverage && type <= Highlight_BelowStdDev3) ||
        (type >= Highlight_Duplicate && type <= Highlight_NoErrors)) {
        return addHighlightCellsRule(type, QString(), QString(), format, stopIfTrue);
    }

    return false;
}

/*!
 * \overload
 *
 * Add a highlight rule with the given \a type, \a formula, \a format and \a stopIfTrue.
 * Return false if failed.
 */
bool ConditionalFormatting::addHighlightCellsRule(HighlightRuleType type,
                                                  const QString &formula,
                                                  const Format &format,
                                                  bool stopIfTrue)
{
    if (type == Highlight_Between || type == Highlight_NotBetween)
        return false;

    return addHighlightCellsRule(type, formula, QString(), format, stopIfTrue);
}

/*!
 * Add a dataBar rule with the given \a color, \a type1, \a val1
 * , \a type2, \a val2, \a showData and \a stopIfTrue.
 * Return false if failed.
 */
bool ConditionalFormatting::addDataBarRule(const QColor &color,
                                           ValueObjectType type1,
                                           const QString &val1,
                                           ValueObjectType type2,
                                           const QString &val2,
                                           bool showData,
                                           bool stopIfTrue)
{
    auto cfRule = std::make_shared<XlsxCfRuleData>();

    cfRule->attrs[XlsxCfRuleData::A_type]   = QStringLiteral("dataBar");
    cfRule->attrs[XlsxCfRuleData::A_color1] = XlsxColor(color);
    if (stopIfTrue)
        cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true;
    if (!showData)
        cfRule->attrs[XlsxCfRuleData::A_hideData] = true;

    XlsxCfVoData cfvo1(type1, val1);
    XlsxCfVoData cfvo2(type2, val2);
    cfRule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(cfvo1);
    cfRule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(cfvo2);

    d->cfRules.append(cfRule);
    return true;
}

/*!
 * \overload
 * Add a dataBar rule with the given \a color, \a showData and \a stopIfTrue.
 */
bool ConditionalFormatting::addDataBarRule(const QColor &color, bool showData, bool stopIfTrue)
{
    return addDataBarRule(
        color, VOT_Min, QStringLiteral("0"), VOT_Max, QStringLiteral("0"), showData, stopIfTrue);
}

/*!
 * Add a colorScale rule with the given \a minColor, \a maxColor and \a stopIfTrue.
 * Return false if failed.
 */
bool ConditionalFormatting::add2ColorScaleRule(const QColor &minColor,
                                               const QColor &maxColor,
                                               bool stopIfTrue)
{
    ValueObjectType type1 = VOT_Min;
    ValueObjectType type2 = VOT_Max;
    QString val1          = QStringLiteral("0");
    QString val2          = QStringLiteral("0");

    auto cfRule = std::make_shared<XlsxCfRuleData>();

    cfRule->attrs[XlsxCfRuleData::A_type]   = QStringLiteral("colorScale");
    cfRule->attrs[XlsxCfRuleData::A_color1] = XlsxColor(minColor);
    cfRule->attrs[XlsxCfRuleData::A_color2] = XlsxColor(maxColor);
    if (stopIfTrue)
        cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true;

    XlsxCfVoData cfvo1(type1, val1);
    XlsxCfVoData cfvo2(type2, val2);
    cfRule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(cfvo1);
    cfRule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(cfvo2);

    d->cfRules.append(cfRule);
    return true;
}

/*!
 * Add a colorScale rule with the given \a minColor, \a midColor, \a maxColor and \a stopIfTrue.
 * Return false if failed.
 */
bool ConditionalFormatting::add3ColorScaleRule(const QColor &minColor,
                                               const QColor &midColor,
                                               const QColor &maxColor,
                                               bool stopIfTrue)
{
    ValueObjectType type1 = VOT_Min;
    ValueObjectType type2 = VOT_Percent;
    ValueObjectType type3 = VOT_Max;
    QString val1          = QStringLiteral("0");
    QString val2          = QStringLiteral("50");
    QString val3          = QStringLiteral("0");

    auto cfRule = std::make_shared<XlsxCfRuleData>();

    cfRule->attrs[XlsxCfRuleData::A_type]   = QStringLiteral("colorScale");
    cfRule->attrs[XlsxCfRuleData::A_color1] = XlsxColor(minColor);
    cfRule->attrs[XlsxCfRuleData::A_color2] = XlsxColor(midColor);
    cfRule->attrs[XlsxCfRuleData::A_color3] = XlsxColor(maxColor);

    if (stopIfTrue)
        cfRule->attrs[XlsxCfRuleData::A_stopIfTrue] = true;

    XlsxCfVoData cfvo1(type1, val1);
    XlsxCfVoData cfvo2(type2, val2);
    XlsxCfVoData cfvo3(type3, val3);
    cfRule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(cfvo1);
    cfRule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(cfvo2);
    cfRule->attrs[XlsxCfRuleData::A_cfvo3] = QVariant::fromValue(cfvo3);

    d->cfRules.append(cfRule);
    return true;
}

/*!
    Returns the ranges on which the validation will be applied.
 */
QList<CellRange> ConditionalFormatting::ranges() const
{
    return d->ranges;
}

/*!
    Add the \a cell on which the conditional formatting will apply to.
 */
void ConditionalFormatting::addCell(const CellReference &cell)
{
    d->ranges.append(CellRange(cell, cell));
}

/*!
    \overload
    Add the cell(\a row, \a col) on which the conditional formatting will apply to.
 */
void ConditionalFormatting::addCell(int row, int col)
{
    d->ranges.append(CellRange(row, col, row, col));
}

/*!
    \overload
    Add the range(\a firstRow, \a firstCol, \a lastRow, \a lastCol) on
    which the conditional formatting will apply to.
 */
void ConditionalFormatting::addRange(int firstRow, int firstCol, int lastRow, int lastCol)
{
    d->ranges.append(CellRange(firstRow, firstCol, lastRow, lastCol));
}

/*!
    Add the \a range on which the conditional formatting will apply to.
 */
void ConditionalFormatting::addRange(const CellRange &range)
{
    d->ranges.append(range);
}

bool ConditionalFormattingPrivate::readCfRule(QXmlStreamReader &reader,
                                              XlsxCfRuleData *rule,
                                              Styles *styles)
{
    Q_ASSERT(reader.name() == QLatin1String("cfRule"));
    QXmlStreamAttributes attrs = reader.attributes();
    if (attrs.hasAttribute(QLatin1String("type")))
        rule->attrs[XlsxCfRuleData::A_type] = attrs.value(QLatin1String("type")).toString();
    if (attrs.hasAttribute(QLatin1String("dxfId"))) {
        int id = attrs.value(QLatin1String("dxfId")).toInt();
        if (styles)
            rule->dxfFormat = styles->dxfFormat(id);
        else
            rule->dxfFormat.setDxfIndex(id);
    }
    rule->priority = attrs.value(QLatin1String("priority")).toInt();
    if (attrs.value(QLatin1String("stopIfTrue")) == QLatin1String("1")) {
        // default is false
        rule->attrs[XlsxCfRuleData::A_stopIfTrue] = QLatin1String("1");
    }
    if (attrs.value(QLatin1String("aboveAverage")) == QLatin1String("0")) {
        // default is true
        rule->attrs[XlsxCfRuleData::A_aboveAverage] = QLatin1String("0");
    }
    if (attrs.value(QLatin1String("percent")) == QLatin1String("1")) {
        // default is false
        rule->attrs[XlsxCfRuleData::A_percent] = QLatin1String("1");
    }
    if (attrs.value(QLatin1String("bottom")) == QLatin1String("1")) {
        // default is false
        rule->attrs[XlsxCfRuleData::A_bottom] = QLatin1String("1");
    }
    if (attrs.hasAttribute(QLatin1String("operator")))
        rule->attrs[XlsxCfRuleData::A_operator] = attrs.value(QLatin1String("operator")).toString();

    if (attrs.hasAttribute(QLatin1String("text")))
        rule->attrs[XlsxCfRuleData::A_text] = attrs.value(QLatin1String("text")).toString();

    if (attrs.hasAttribute(QLatin1String("timePeriod")))
        rule->attrs[XlsxCfRuleData::A_timePeriod] =
            attrs.value(QLatin1String("timePeriod")).toString();

    if (attrs.hasAttribute(QLatin1String("rank")))
        rule->attrs[XlsxCfRuleData::A_rank] = attrs.value(QLatin1String("rank")).toString();

    if (attrs.hasAttribute(QLatin1String("stdDev")))
        rule->attrs[XlsxCfRuleData::A_stdDev] = attrs.value(QLatin1String("stdDev")).toString();

    if (attrs.value(QLatin1String("equalAverage")) == QLatin1String("1")) {
        // default is false
        rule->attrs[XlsxCfRuleData::A_equalAverage] = QLatin1String("1");
    }

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("formula")) {
                const QString f = reader.readElementText();
                if (!rule->attrs.contains(XlsxCfRuleData::A_formula1))
                    rule->attrs[XlsxCfRuleData::A_formula1] = f;
                else if (!rule->attrs.contains(XlsxCfRuleData::A_formula2))
                    rule->attrs[XlsxCfRuleData::A_formula2] = f;
                else if (!rule->attrs.contains(XlsxCfRuleData::A_formula3))
                    rule->attrs[XlsxCfRuleData::A_formula3] = f;
            } else if (reader.name() == QLatin1String("dataBar")) {
                readCfDataBar(reader, rule);
            } else if (reader.name() == QLatin1String("colorScale")) {
                readCfColorScale(reader, rule);
            }
        }
        if (reader.tokenType() == QXmlStreamReader::EndElement &&
            reader.name() == QStringLiteral("conditionalFormatting")) {
            break;
        }
    }
    return true;
}

bool ConditionalFormattingPrivate::readCfDataBar(QXmlStreamReader &reader, XlsxCfRuleData *rule)
{
    Q_ASSERT(reader.name() == QLatin1String("dataBar"));
    QXmlStreamAttributes attrs = reader.attributes();
    if (attrs.value(QLatin1String("showValue")) == QLatin1String("0"))
        rule->attrs[XlsxCfRuleData::A_hideData] = QStringLiteral("1");

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("cfvo")) {
                XlsxCfVoData data;
                readCfVo(reader, data);
                if (!rule->attrs.contains(XlsxCfRuleData::A_cfvo1))
                    rule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(data);
                else
                    rule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(data);
            } else if (reader.name() == QLatin1String("color")) {
                XlsxColor color;
                color.loadFromXml(reader);
                rule->attrs[XlsxCfRuleData::A_color1] = color;
            }
        }
        if (reader.tokenType() == QXmlStreamReader::EndElement &&
            reader.name() == QStringLiteral("dataBar")) {
            break;
        }
    }

    return true;
}

bool ConditionalFormattingPrivate::readCfColorScale(QXmlStreamReader &reader, XlsxCfRuleData *rule)
{
    Q_ASSERT(reader.name() == QLatin1String("colorScale"));

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("cfvo")) {
                XlsxCfVoData data;
                readCfVo(reader, data);
                if (!rule->attrs.contains(XlsxCfRuleData::A_cfvo1))
                    rule->attrs[XlsxCfRuleData::A_cfvo1] = QVariant::fromValue(data);
                else if (!rule->attrs.contains(XlsxCfRuleData::A_cfvo2))
                    rule->attrs[XlsxCfRuleData::A_cfvo2] = QVariant::fromValue(data);
                else
                    rule->attrs[XlsxCfRuleData::A_cfvo3] = QVariant::fromValue(data);
            } else if (reader.name() == QLatin1String("color")) {
                XlsxColor color;
                color.loadFromXml(reader);
                if (!rule->attrs.contains(XlsxCfRuleData::A_color1))
                    rule->attrs[XlsxCfRuleData::A_color1] = color;
                else if (!rule->attrs.contains(XlsxCfRuleData::A_color2))
                    rule->attrs[XlsxCfRuleData::A_color2] = color;
                else
                    rule->attrs[XlsxCfRuleData::A_color3] = color;
            }
        }
        if (reader.tokenType() == QXmlStreamReader::EndElement &&
            reader.name() == QStringLiteral("colorScale")) {
            break;
        }
    }

    return true;
}

bool ConditionalFormattingPrivate::readCfVo(QXmlStreamReader &reader, XlsxCfVoData &cfvo)
{
    Q_ASSERT(reader.name() == QStringLiteral("cfvo"));

    QXmlStreamAttributes attrs = reader.attributes();

    QString type = attrs.value(QLatin1String("type")).toString();
    ConditionalFormatting::ValueObjectType t;
    if (type == QLatin1String("formula"))
        t = ConditionalFormatting::VOT_Formula;
    else if (type == QLatin1String("max"))
        t = ConditionalFormatting::VOT_Max;
    else if (type == QLatin1String("min"))
        t = ConditionalFormatting::VOT_Min;
    else if (type == QLatin1String("num"))
        t = ConditionalFormatting::VOT_Num;
    else if (type == QLatin1String("percent"))
        t = ConditionalFormatting::VOT_Percent;
    else // if (type == QLatin1String("percentile"))
        t = ConditionalFormatting::VOT_Percentile;

    cfvo.type  = t;
    cfvo.value = attrs.value(QLatin1String("val")).toString();
    if (attrs.value(QLatin1String("gte")) == QLatin1String("0")) {
        // default is true
        cfvo.gte = false;
    }
    return true;
}

bool ConditionalFormatting::loadFromXml(QXmlStreamReader &reader, Styles *styles)
{
    Q_ASSERT(reader.name() == QStringLiteral("conditionalFormatting"));

    d->ranges.clear();
    d->cfRules.clear();
    QXmlStreamAttributes attrs = reader.attributes();
    const QString sqref        = attrs.value(QLatin1String("sqref")).toString();
    const auto sqrefParts      = sqref.split(QLatin1Char(' '));
    for (const QString &range : sqrefParts) {
        this->addRange(range);
    }

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("cfRule")) {
                auto cfRule = std::make_shared<XlsxCfRuleData>();
                d->readCfRule(reader, cfRule.get(), styles);
                d->cfRules.append(cfRule);
            }
        }
        if (reader.tokenType() == QXmlStreamReader::EndElement &&
            reader.name() == QStringLiteral("conditionalFormatting")) {
            break;
        }
    }

    return true;
}

bool ConditionalFormatting::saveToXml(QXmlStreamWriter &writer) const
{
    writer.writeStartElement(QStringLiteral("conditionalFormatting"));
    QStringList sqref;
    const auto rangeList = ranges();
    for (const CellRange &range : rangeList) {
        sqref.append(range.toString());
    }
    writer.writeAttribute(QStringLiteral("sqref"), sqref.join(QLatin1String(" ")));

    for (int i = 0; i < d->cfRules.size(); ++i) {
        const std::shared_ptr<XlsxCfRuleData> &rule = d->cfRules[i];
        writer.writeStartElement(QStringLiteral("cfRule"));
        writer.writeAttribute(QStringLiteral("type"),
                              rule->attrs[XlsxCfRuleData::A_type].toString());
        if (rule->dxfFormat.dxfIndexValid())
            writer.writeAttribute(QStringLiteral("dxfId"),
                                  QString::number(rule->dxfFormat.dxfIndex()));
        writer.writeAttribute(QStringLiteral("priority"), QString::number(rule->priority));

        auto it = rule->attrs.constFind(XlsxCfRuleData::A_stopIfTrue);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("stopIfTrue"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_aboveAverage);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("aboveAverage"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_percent);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("percent"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_bottom);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("bottom"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_operator);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("operator"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_text);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("text"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_timePeriod);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("timePeriod"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_rank);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("rank"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_stdDev);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("stdDev"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_equalAverage);
        if (it != rule->attrs.constEnd())
            writer.writeAttribute(QStringLiteral("equalAverage"), it.value().toString());

        if (rule->attrs[XlsxCfRuleData::A_type] == QLatin1String("dataBar")) {
            writer.writeStartElement(QStringLiteral("dataBar"));
            if (rule->attrs.contains(XlsxCfRuleData::A_hideData))
                writer.writeAttribute(QStringLiteral("showValue"), QStringLiteral("0"));
            d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo1].value<XlsxCfVoData>());
            d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo2].value<XlsxCfVoData>());
            rule->attrs[XlsxCfRuleData::A_color1].value<XlsxColor>().saveToXml(writer);
            writer.writeEndElement(); // dataBar
        } else if (rule->attrs[XlsxCfRuleData::A_type] == QLatin1String("colorScale")) {
            writer.writeStartElement(QStringLiteral("colorScale"));
            d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo1].value<XlsxCfVoData>());
            d->writeCfVo(writer, rule->attrs[XlsxCfRuleData::A_cfvo2].value<XlsxCfVoData>());

            it = rule->attrs.constFind(XlsxCfRuleData::A_cfvo3);
            if (it != rule->attrs.constEnd())
                d->writeCfVo(writer, it.value().value<XlsxCfVoData>());

            rule->attrs[XlsxCfRuleData::A_color1].value<XlsxColor>().saveToXml(writer);
            rule->attrs[XlsxCfRuleData::A_color2].value<XlsxColor>().saveToXml(writer);

            it = rule->attrs.constFind(XlsxCfRuleData::A_color3);
            if (it != rule->attrs.constEnd())
                it.value().value<XlsxColor>().saveToXml(writer);

            writer.writeEndElement(); // colorScale
        }

        it = rule->attrs.constFind(XlsxCfRuleData::A_formula1_temp);
        if (it != rule->attrs.constEnd()) {
            const auto _ranges = ranges();
            const auto begin   = _ranges.begin();
            if (begin != _ranges.end()) {
                QString str       = begin->toString();
                QString startCell = str.mid(0, str.indexOf(u':'));
                writer.writeTextElement(QStringLiteral("formula"),
                                        it.value().toString().arg(startCell));
            }
        } else {
            it = rule->attrs.constFind(XlsxCfRuleData::A_formula1);
            if (it != rule->attrs.constEnd()) {
                writer.writeTextElement(QStringLiteral("formula"), it.value().toString());
            }
        }

        it = rule->attrs.constFind(XlsxCfRuleData::A_formula2);
        if (it != rule->attrs.constEnd())
            writer.writeTextElement(QStringLiteral("formula"), it.value().toString());

        it = rule->attrs.constFind(XlsxCfRuleData::A_formula3);
        if (it != rule->attrs.constEnd())
            writer.writeTextElement(QStringLiteral("formula"), it.value().toString());

        writer.writeEndElement(); // cfRule
    }

    writer.writeEndElement(); // conditionalFormatting
    return true;
}

QT_END_NAMESPACE_XLSX
