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

#include "qmlsensor_p.h"
#include <QtSensors/QSensor>
#include <QDebug>
#include <QtCore/private/qobject_p.h>

QT_BEGIN_NAMESPACE

class QmlSensorPrivate : public QObjectPrivate
{
    Q_DECLARE_PUBLIC(QmlSensor)
public:

    QList<QmlSensorRange *> availableRanges;
    QList<QmlSensorOutputRange *> outputRanges;
};

template<typename Item>
qsizetype readonlyListCount(QQmlListProperty<Item> *p)
{
    return static_cast<const QList<Item *> *>(p->data)->size();
}

template<typename Item>
Item *readonlyListAt(QQmlListProperty<Item> *p, qsizetype idx)
{
    return static_cast<const QList<Item *> *>(p->data)->at(idx);
};

template<typename Item>
QQmlListProperty<Item> readonlyListProperty(const QObject *o, const QList<Item *> *list)
{
    // Unfortunately QQmlListProperty won't accept a const object, even on the readonly ctor.
    return QQmlListProperty<Item>(const_cast<QObject *>(o), const_cast<QList<Item *> *>(list),
                                  readonlyListCount<Item>, readonlyListAt<Item>);
}

/*!
    \qmltype Sensor
//!    \instantiates QmlSensor
    \inqmlmodule QtSensors
    \since QtSensors 5.0
    \brief The Sensor element serves as a base type for sensors.

    The Sensor element serves as a base type for sensors.

    This element wraps the QSensor class. Please see the documentation for
    QSensor for details.

    This element cannot be directly created. Please use one of the sub-classes instead.
*/

QmlSensor::QmlSensor(QObject *parent)
    : QObject(*(new QmlSensorPrivate), parent)
{
}

QmlSensor::~QmlSensor()
{
}

/*!
    \qmlproperty string Sensor::identifier
    This property holds the backend identifier for the sensor.

    Please see QSensor::identifier for information about this property.
*/

QByteArray QmlSensor::identifier() const
{
    return sensor()->identifier();
}

void QmlSensor::setIdentifier(const QByteArray &identifier)
{
    sensor()->setIdentifier(identifier);
}

/*!
    \qmlproperty string Sensor::type
    This property holds the type of the sensor.
*/

QByteArray QmlSensor::type() const
{
    return sensor()->type();
}

/*!
    \qmlproperty bool Sensor::connectedToBackend
    This property holds a value indicating if the sensor has connected to a backend.

    Please see QSensor::connectedToBackend for information about this property.
*/

bool QmlSensor::isConnectedToBackend() const
{
    return sensor()->isConnectedToBackend();
}

/*!
    \qmlproperty bool Sensor::busy
    This property holds a value to indicate if the sensor is busy.

    Please see QSensor::busy for information about this property.
*/

bool QmlSensor::isBusy() const
{
    return sensor()->isBusy();
}

/*!
    \qmlproperty bool Sensor::active
    This property holds a value to indicate if the sensor is active.

    Please see QSensor::active for information about this property.
*/

void QmlSensor::setActive(bool active)
{
    if (!m_componentComplete) {
        m_activateOnComplete = active;
        return;
    }
    if (active)
        sensor()->start();
    else
        sensor()->stop();
}

bool QmlSensor::isActive() const
{
    return sensor()->isActive();
}

/*!
    \qmlproperty bool Sensor::alwaysOn
    This property holds a value to indicate if the sensor should remain running when the screen is off.

    Please see QSensor::alwaysOn for information about this property.
*/

bool QmlSensor::isAlwaysOn() const
{
    return sensor()->isAlwaysOn();
}

void QmlSensor::setAlwaysOn(bool alwaysOn)
{
    sensor()->setAlwaysOn(alwaysOn);
}

/*!
    \qmlproperty bool Sensor::skipDuplicates
    \since QtSensors 5.1

    This property indicates whether duplicate reading values should be omitted.

    Please see QSensor::skipDuplicates for information about this property.
*/

bool QmlSensor::skipDuplicates() const
{
    return sensor()->skipDuplicates();
}

void QmlSensor::setSkipDuplicates(bool skipDuplicates)
{
    sensor()->setSkipDuplicates(skipDuplicates);
}

/*!
    \qmlproperty list<Range> Sensor::availableDataRates
    This property holds the data rates that the sensor supports.

    Please see QSensor::availableDataRates for information about this property.
*/
QQmlListProperty<QmlSensorRange> QmlSensor::availableDataRates() const
{
    Q_D(const QmlSensor);
    return readonlyListProperty<QmlSensorRange>(this, &d->availableRanges);
}

/*!
    \qmlproperty int Sensor::dataRate
    This property holds the data rate that the sensor should be run at.

    Please see QSensor::dataRate for information about this property.
*/

int QmlSensor::dataRate() const
{
    return sensor()->dataRate();
}

void QmlSensor::setDataRate(int rate)
{
    if (rate != dataRate()) {
      sensor()->setDataRate(rate);
      Q_EMIT dataRateChanged();
    }
}

/*!
    \qmlproperty list<OutputRange> Sensor::outputRanges
    This property holds a list of output ranges the sensor supports.

    Please see QSensor::outputRanges for information about this property.
*/

QQmlListProperty<QmlSensorOutputRange> QmlSensor::outputRanges() const
{
    Q_D(const QmlSensor);
    return readonlyListProperty<QmlSensorOutputRange>(this, &d->outputRanges);
}

/*!
    \qmlproperty int Sensor::outputRange
    This property holds the output range in use by the sensor.

    Please see QSensor::outputRange for information about this property.
*/

int QmlSensor::outputRange() const
{
    return sensor()->outputRange();
}

void QmlSensor::setOutputRange(int index)
{
    int oldRange = outputRange();
    if (oldRange == index) return;
    sensor()->setOutputRange(index);
    if (sensor()->outputRange() == index)
        Q_EMIT outputRangeChanged();
}

/*!
    \qmlproperty string Sensor::description
    This property holds a descriptive string for the sensor.
*/

QString QmlSensor::description() const
{
    return sensor()->description();
}

/*!
    \qmlproperty int Sensor::error
    This property holds the last error code set on the sensor.
*/

int QmlSensor::error() const
{
    return sensor()->error();
}

/*!
    \qmlproperty SensorReading Sensor::reading
    This property holds the reading class.

    Please see QSensor::reading for information about this property.
    \sa {QML Reading types}
*/

QmlSensorReading *QmlSensor::reading() const
{
    return m_reading;
}

QBindable<QmlSensorReading*> QmlSensor::bindableReading() const
{
    return &m_reading;
}

/*!
    \qmlmethod bool Sensor::isFeatureSupported(feature)
    \since QtSensors 6.7
    Checks if a specific feature is supported by the backend.
    Returns \c true if the \a feature is supported, and \c false otherwise.
    For feature descriptions see \l {QSensor::Feature}.

    Please see QSensor::isFeatureSupported for information.
*/

bool QmlSensor::isFeatureSupported(Feature feature) const
{
    return sensor()->isFeatureSupported(static_cast<QSensor::Feature>(feature));
}

/*!
    \qmlproperty Sensor::AxesOrientationMode Sensor::axesOrientationMode
    \since QtSensors 5.1
    This property holds the mode that affects how the screen orientation changes reading values.

    Please see QSensor::axesOrientationMode for information about this property.
*/

QmlSensor::AxesOrientationMode QmlSensor::axesOrientationMode() const
{
    return static_cast<QmlSensor::AxesOrientationMode>(sensor()->axesOrientationMode());
}

void QmlSensor::setAxesOrientationMode(QmlSensor::AxesOrientationMode axesOrientationMode)
{
    sensor()->setAxesOrientationMode(static_cast<QSensor::AxesOrientationMode>(axesOrientationMode));
}

/*!
    \qmlproperty int Sensor::currentOrientation
    \since QtSensors 5.1
    This property holds the current orientation that is used for rotating the reading values.

    Please see QSensor::currentOrientation for information about this property.
*/

int QmlSensor::currentOrientation() const
{
    return sensor()->currentOrientation();
}

/*!
    \qmlproperty int Sensor::userOrientation
    \since QtSensors 5.1
    This property holds the angle used for rotating the reading values in the UserOrientation mode.

    Please see QSensor::userOrientation for information about this property.
*/

int QmlSensor::userOrientation() const
{
    return sensor()->userOrientation();
}

void QmlSensor::setUserOrientation(int userOrientation)
{
    sensor()->setUserOrientation(userOrientation);
}

/*!
    \qmlproperty int Sensor::maxBufferSize
    \since QtSensors 5.1
    This property holds the maximum buffer size.

    Please see QSensor::maxBufferSize for information about this property.
*/

int QmlSensor::maxBufferSize() const
{
    return sensor()->maxBufferSize();
}

/*!
    \qmlproperty int Sensor::efficientBufferSize
    \since QtSensors 5.1
    The property holds the most efficient buffer size.

    Please see QSensor::efficientBufferSize for information about this property.
*/

int QmlSensor::efficientBufferSize() const
{
    return sensor()->efficientBufferSize();
}

/*!
    \qmlproperty int Sensor::bufferSize
    \since QtSensors 5.1
    This property holds the size of the buffer.

    Please see QSensor::bufferSize for information about this property.
*/

int QmlSensor::bufferSize() const
{
    return sensor()->bufferSize();
}

void QmlSensor::setBufferSize(int bufferSize)
{
    sensor()->setBufferSize(bufferSize);
}

/*!
    \qmlmethod bool Sensor::start()
    Start retrieving values from the sensor. Returns true if the sensor
    was started, false otherwise.

    Please see QSensor::start() for information.
*/

bool QmlSensor::start()
{
    return sensor()->start();
}

/*!
    \qmlmethod bool Sensor::stop()
    Stop retrieving values from the sensor.
    Returns true if the sensor was stopped, false otherwise.

    Please see QSensor::stop() for information.
*/

void QmlSensor::stop()
{
    setActive(false);
}

void QmlSensor::classBegin()
{
}

void QmlSensor::componentComplete()
{
    m_componentComplete = true;

    connect(sensor(), SIGNAL(sensorError(int)), this, SIGNAL(errorChanged()));
    connect(sensor(), SIGNAL(activeChanged()), this, SIGNAL(activeChanged()));
    connect(sensor(), SIGNAL(alwaysOnChanged()), this, SIGNAL(alwaysOnChanged()));
    connect(sensor(), SIGNAL(skipDuplicatesChanged(bool)), this, SIGNAL(skipDuplicatesChanged(bool)));
    connect(sensor(), SIGNAL(axesOrientationModeChanged(AxesOrientationMode)),
            this, SIGNAL(axesOrientationModeChanged(AxesOrientationMode)));
    connect(sensor(), SIGNAL(userOrientationChanged(int)), this, SIGNAL(userOrientationChanged(int)));
    connect(sensor(), SIGNAL(currentOrientationChanged(int)), this, SIGNAL(currentOrientationChanged(int)));
    connect(sensor(), SIGNAL(bufferSizeChanged(int)), this, SIGNAL(bufferSizeChanged(int)));
    connect(sensor(), SIGNAL(maxBufferSizeChanged(int)), this, SIGNAL(maxBufferSizeChanged(int)));
    connect(sensor(), SIGNAL(efficientBufferSizeChanged(int)), this, SIGNAL(efficientBufferSizeChanged(int)));
    connect(sensor(), &QSensor::busyChanged, this, &QmlSensor::busyChanged);
    connect(sensor(), &QSensor::identifierChanged, this, &QmlSensor::identifierChanged);

    // These can change!
    int oldDataRate = dataRate();
    int oldOutputRange = outputRange();

    if (sensor()->connectToBackend())
        Q_EMIT connectedToBackendChanged();

    m_reading.setValueBypassingBindings(createReading());
    m_reading->setParent(this);
    if (oldDataRate != dataRate())
        Q_EMIT dataRateChanged();
    if (oldOutputRange != outputRange())
        Q_EMIT outputRangeChanged();

    Q_D(QmlSensor);
    const auto available = sensor()->availableDataRates();
    d->availableRanges.reserve(available.size());
    for (const qrange &r : available) {
        auto *range = new QmlSensorRange(this);
        range->setMinumum(r.first);
        range->setMaximum(r.second);
        d->availableRanges.append(range);
    }
    const auto output = sensor()->outputRanges();
    d->outputRanges.reserve(output.size());
    for (const qoutputrange &r : output) {
        auto *range = new QmlSensorOutputRange(this);
        range->setMinimum(r.minimum);
        range->setMaximum(r.maximum);
        range->setAccuracy(r.accuracy);
        d->outputRanges.append(range);
    }

    // meta-data should become non-empty
    if (!description().isEmpty())
        Q_EMIT descriptionChanged();
    if (available.size())
        Q_EMIT availableDataRatesChanged();
    if (output.size())
        Q_EMIT outputRangesChanged();

    connect(sensor(), SIGNAL(readingChanged()), this, SLOT(updateReading()));
    if (m_activateOnComplete)
        start();
}

void QmlSensor::updateReading()
{
    if (m_reading) {
        m_reading->update();
        m_reading.notify();
        Q_EMIT readingChanged();
    }
}

/*!
    \qmltype SensorReading
//!    \instantiates QmlSensorReading
    \inqmlmodule QtSensors
    \since QtSensors 5.0
    \brief The SensorReading element serves as a base type for sensor readings.

    The SensorReading element serves as a base type for sensor readings.

    This element wraps the QSensorReading class. Please see the documentation for
    QSensorReading for details.

    This element cannot be directly created.
*/

/*!
    \qmlproperty quint64 SensorReading::timestamp
    A timestamp for the reading.

    Please see QSensorReading::timestamp for information about this property.
*/

quint64 QmlSensorReading::timestamp() const
{
    return m_timestamp;
}

QBindable<quint64> QmlSensorReading::bindableTimestamp() const
{
    return &m_timestamp;
}


void QmlSensorReading::update()
{
    m_timestamp = reading()->timestamp();
    readingUpdate();
}

QT_END_NAMESPACE
