// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qvalue3daxis_p.h"
#include "qvalue3daxisformatter_p.h"

QT_BEGIN_NAMESPACE

/*!
 * \class QValue3DAxis
 * \inmodule QtGraphs
 * \ingroup graphs_3D
 * \brief The QValue3DAxis class manipulates an axis of a graph.
 *
 * A value axis can be given a range of values and segment and subsegment
 * counts to divide the range into.
 *
 * Labels are drawn between each segment, and grid lines are drawn between each
 * segment and each subsegment. \note If visible, there will always be at least
 * two grid lines and labels indicating the minimum and maximum values of
 * the range, as there is always at least one segment.
 */

/*!
 * \qmltype Value3DAxis
 * \inqmlmodule QtGraphs
 * \ingroup graphs_qml_3D
 * \nativetype QValue3DAxis
 * \inherits Abstract3DAxis
 * \brief Manipulates an axis of a graph.
 *
 * This type provides an axis that can be given a range of values and segment
 * and subsegment counts to divide the range into.
 */

/*!
 * \qmlproperty qsizetype Value3DAxis::segmentCount
 *
 * The number of segments on the axis. This indicates how many labels are drawn.
 * The number of grid lines to be drawn is calculated with the following
 * formula: \c {segments * subsegments + 1}. The preset default is \c 5. The
 * value cannot be below \c 1.
 */

/*!
 * \qmlproperty qsizetype Value3DAxis::subSegmentCount
 *
 * The number of subsegments inside each segment on the axis. Grid lines are
 * drawn between each subsegment, in addition to each segment. The preset
 * default is \c 1. The value cannot be below \c 1.
 */

/*!
 * \qmlproperty string Value3DAxis::labelFormat
 *
 * The label format to be used for the labels on this axis.
 *
 * The format string supports the following conversion specifiers, length
 * modifiers, and flags provided by \c printf() in the standard C++ library:
 * d, i, o, x, X, f, F, e, E, g, G, c.
 *
 * If GraphsItem3D::locale is anything else than \c{"C"}, the supported
 * specifiers are limited to: d, e, E, f, g, G, and i. Also, only the precision
 * modifier is supported. The rest of the formatting comes from the default
 * \l [QML] Locale of the application.
 *
 * \sa GraphsItem3D::locale
 */

/*!
 * \qmlproperty Value3DAxisFormatter Value3DAxis::formatter
 *
 * The axis formatter to be used. Any existing formatter is deleted when a new
 * formatter is set.
 *
 */

/*!
 * \qmlproperty bool Value3DAxis::reversed
 *
 * If \c{true}, the axis will be rendered in reverse. That is, the positions of
 * the minimum and maximum values are swapped when the graph is rendered. This
 * property does not affect the actual minimum and maximum values of the axis.
 */

/*!
    \qmlsignal Value3DAxis::segmentCountChanged(qsizetype count)

    This signal is emitted when segmentCount changes to \a count.
*/
/*!
    \qmlsignal Value3DAxis::subSegmentCountChanged(qsizetype count)

    This signal is emitted when subSegmentCount changes to \a count.
*/
/*!
    \qmlsignal Value3DAxis::labelFormatChanged(string format)

    This signal is emitted when labelFormat changes to \a format.
*/
/*!
    \qmlsignal Value3DAxis::formatterChanged(Value3DAxisFormatter formatter)

    This signal is emitted when \l formatter changes to \a formatter.
*/
/*!
    \qmlsignal Value3DAxis::reversedChanged(bool enable)

    This signal is emitted when \l reversed changes to \a enable.
*/

/*!
 * Constructs QValue3DAxis with the given \a parent.
 */
QValue3DAxis::QValue3DAxis(QObject *parent)
    : QAbstract3DAxis(*(new QValue3DAxisPrivate()), parent)
{
    setFormatter(new QValue3DAxisFormatter);
}

/*!
 * Destroys QValue3DAxis.
 */
QValue3DAxis::~QValue3DAxis() {}

/*!
 * \property QValue3DAxis::segmentCount
 *
 * \brief The number of segments on the axis.
 *
 * This indicates how many labels are drawn. The number
 * of grid lines to be drawn is calculated with formula: \c {segments *
 * subsegments + 1}. The preset default is \c 5. The value cannot be below \c 1.
 *
 * \sa setSubSegmentCount()
 */
void QValue3DAxis::setSegmentCount(qsizetype count)
{
    Q_D(QValue3DAxis);
    if (count <= 0) {
        qWarning(
            "Warning: Illegal segment count automatically adjusted to a legal one: %" PRIdQSIZETYPE
            " --> 1",
            count);
        count = 1;
    }
    if (d->m_segmentCount != count) {
        d->m_segmentCount = count;
        d->emitLabelsChanged();
        emit segmentCountChanged(count);
    }
}

qsizetype QValue3DAxis::segmentCount() const
{
    Q_D(const QValue3DAxis);
    return d->m_segmentCount;
}

/*!
 * \property QValue3DAxis::subSegmentCount
 *
 * \brief The number of subsegments inside each segment on the axis.
 *
 * Grid lines are drawn between
 * each subsegment, in addition to each segment.
 * The preset default is \c 1. The value cannot be below \c 1.
 *
 * \sa setSegmentCount()
 */
void QValue3DAxis::setSubSegmentCount(qsizetype count)
{
    Q_D(QValue3DAxis);
    if (count <= 0) {
        qWarning("Warning: Illegal subsegment count automatically adjusted to a legal one: "
                 "%" PRIdQSIZETYPE " -> 1",
                 count);
        count = 1;
    }
    if (d->m_subSegmentCount != count) {
        d->m_subSegmentCount = count;
        emit subSegmentCountChanged(count);
    }
}

qsizetype QValue3DAxis::subSegmentCount() const
{
    Q_D(const QValue3DAxis);
    return d->m_subSegmentCount;
}

/*!
 * \property QValue3DAxis::labelFormat
 *
 * \brief The label format to be used for the labels on this axis.
 *
 * The format string supports the following conversion specifiers, length
 * modifiers, and flags provided by \c printf() in the standard C++ library:
 * d, i, o, x, X, f, F, e, E, g, G, c.
 *
 * If Q3DGraphsWidgetItem::locale is anything else than \c{"C"}, the supported
 * specifiers are limited to: d, e, E, f, g, G, and i. Also, only the precision
 * modifier is supported. The rest of the formatting comes from the default
 * QLocale of the application.
 *
 * Usage example:
 *
 * \c {axis->setLabelFormat("%.2f mm");}
 *
 * \sa formatter, Q3DGraphsWidgetItem::locale
 */
void QValue3DAxis::setLabelFormat(const QString &format)
{
    Q_D(QValue3DAxis);
    if (d->m_labelFormat != format) {
        d->m_labelFormat = format;
        d->emitLabelsChanged();
        emit labelFormatChanged(format);
    }
}

QString QValue3DAxis::labelFormat() const
{
    Q_D(const QValue3DAxis);
    return d->m_labelFormat;
}

/*!
 * \property QValue3DAxis::formatter
 *
 * \brief The axis formatter to be used.
 *
 * Any existing formatter is deleted when a new formatter
 * is set.
 */
void QValue3DAxis::setFormatter(QValue3DAxisFormatter *formatter)
{
    Q_ASSERT(formatter);
    Q_D(QValue3DAxis);

    if (formatter != d->m_formatter) {
        delete d->m_formatter;
        d->m_formatter = formatter;
        formatter->setParent(this);
        formatter->setAxis(this);
        emit formatterChanged(formatter);
        emit formatterDirty();
    }
}

QValue3DAxisFormatter *QValue3DAxis::formatter() const
{
    Q_D(const QValue3DAxis);
    return d->m_formatter;
}

/*!
 * \property QValue3DAxis::reversed
 *
 * \brief Whether the axis is rendered in reverse.
 *
 * If \c{true}, the axis will be rendered in reverse, which means the positions
 * of minimum and maximum values are swapped when the graph is rendered. This
 * property doesn't affect the actual minimum and maximum values of the axis.
 */
void QValue3DAxis::setReversed(bool enable)
{
    Q_D(QValue3DAxis);
    if (d->m_reversed != enable) {
        d->m_reversed = enable;
        emit reversedChanged(enable);
    }
}

bool QValue3DAxis::reversed() const
{
    Q_D(const QValue3DAxis);
    return d->m_reversed;
}

void QValue3DAxis::recalculate()
{
    formatter()->d_func()->recalculate();
}

qsizetype QValue3DAxis::gridSize()
{
    return formatter()->gridPositions().size();
}

qsizetype QValue3DAxis::subGridSize()
{
    return formatter()->subGridPositions().size();
}

float QValue3DAxis::gridPositionAt(qsizetype gridLine)
{
    return formatter()->gridPositions().at(gridLine);
}

float QValue3DAxis::subGridPositionAt(qsizetype gridLine)
{
    return formatter()->subGridPositions().at(gridLine);
}

float QValue3DAxis::labelPositionAt(qsizetype index)
{
    return formatter()->labelPositions().at(index);
}

float QValue3DAxis::positionAt(float x)
{
    return formatter()->positionAt(x);
}

QString QValue3DAxis::stringForValue(float x)
{
    return formatter()->stringForValue(x, labelFormat());
}

QValue3DAxisPrivate::QValue3DAxisPrivate()
    : QAbstract3DAxisPrivate(QAbstract3DAxis::AxisType::Value)
    , m_segmentCount(5)
    , m_subSegmentCount(1)
    , m_labelFormat(Utils::defaultLabelFormat())
    , m_labelsDirty(true)
    , m_formatter(0)
    , m_reversed(false)
{}

QValue3DAxisPrivate::~QValue3DAxisPrivate() {}

void QValue3DAxisPrivate::setRange(float min, float max, bool suppressWarnings)
{
    bool dirty = (min != m_min || max != m_max);

    QAbstract3DAxisPrivate::setRange(min, max, suppressWarnings);

    if (dirty)
        emitLabelsChanged();
}

void QValue3DAxisPrivate::setMin(float min)
{
    bool dirty = (min != m_min);

    QAbstract3DAxisPrivate::setMin(min);

    if (dirty)
        emitLabelsChanged();
}

void QValue3DAxisPrivate::setMax(float max)
{
    bool dirty = (max != m_max);

    QAbstract3DAxisPrivate::setMax(max);

    if (dirty)
        emitLabelsChanged();
}

void QValue3DAxisPrivate::emitLabelsChanged()
{
    Q_Q(QValue3DAxis);
    m_labelsDirty = true;
    emit q->labelsChanged();
}

void QValue3DAxisPrivate::emitFormatterDirty()
{
    Q_Q(QValue3DAxis);
    m_labelsDirty = true;
    emit q->formatterDirty();
}

void QValue3DAxisPrivate::updateLabels()
{
    if (!m_labelsDirty)
        return;

    m_labelsDirty = false;

    m_formatter->d_func()->recalculate();

    m_labels = m_formatter->labelStrings();
}

bool QValue3DAxisPrivate::allowZero()
{
    return m_formatter->allowZero();
}

bool QValue3DAxisPrivate::allowNegatives()
{
    return m_formatter->allowNegatives();
}

bool QValue3DAxisPrivate::allowMinMaxSame()
{
    return false;
}

QT_END_NAMESPACE
