/*
  Q Light Controller Plus
  vcspeeddial.cpp

  Copyright (c) Massimo Callegari

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QQmlEngine>
#include <QDateTime>
#include <QtMath>

#include <algorithm>

#include "doc.h"
#include "qlcmacros.h"
#include "vcspeeddial.h"
#include "vcspeeddialpreset.h"
#include "tardis.h"

#define INPUT_DIAL_ID           0
#define INPUT_TAP_ID            1
#define INPUT_MULT_ID           2
#define INPUT_DIV_ID            3
#define INPUT_RESET_ID          4
#define INPUT_APPLY_ID          5
#define INPUT_1_16X_ID          6
#define INPUT_1_8X_ID           7
#define INPUT_1_4X_ID           8
#define INPUT_1_2X_ID           9
#define INPUT_2X_ID             10
#define INPUT_4X_ID             11
#define INPUT_8X_ID             12
#define INPUT_16X_ID            13
#define INPUT_PRESETS_BASE_ID   30

VCSpeedDial::VCSpeedDial(Doc *doc, QObject *parent)
    : VCWidget(doc, parent)
    , m_visibilityMask(Tap | Beats | Multipliers)
    , m_timeMinimumValue(0)
    , m_timeMaximumValue(1000 * 10)
    , m_currentTime(0)
    , m_resetOnDialChange(false)
    , m_currentFactor(One)
    , m_lastAssignedPresetId(15)
    , m_lastTap(0)
    , m_tapTimeValue(0)
{
    setType(VCWidget::SpeedWidget);

    cacheMultipliers();

    registerExternalControl(INPUT_DIAL_ID, tr("Time wheel"), false);
    registerExternalControl(INPUT_TAP_ID, tr("Tap Button"), true);
    registerExternalControl(INPUT_MULT_ID, tr("Multiply Button"), true);
    registerExternalControl(INPUT_DIV_ID, tr("Divide Button"), true);
    registerExternalControl(INPUT_RESET_ID, tr("Reset Button"), true);
    registerExternalControl(INPUT_APPLY_ID, tr("Apply Button"), true);
    registerExternalControl(INPUT_1_16X_ID, tr("1/16x Speed Button"), true);
    registerExternalControl(INPUT_1_8X_ID, tr("1/8x Speed Button"), true);
    registerExternalControl(INPUT_1_4X_ID, tr("1/4x Speed Button"), true);
    registerExternalControl(INPUT_1_2X_ID, tr("1/2x Speed Button"), true);
    registerExternalControl(INPUT_2X_ID, tr("2x Speed Button"), true);
    registerExternalControl(INPUT_4X_ID, tr("4x Speed Button"), true);
    registerExternalControl(INPUT_8X_ID, tr("8x Speed Button"), true);
    registerExternalControl(INPUT_16X_ID, tr("16x Speed Button"), true);
}

VCSpeedDial::~VCSpeedDial()
{
    if (m_item)
        delete m_item;

    clearPresets();
}

QString VCSpeedDial::defaultCaption() const
{
    return tr("Speed %1").arg(id() + 1);
}

void VCSpeedDial::setupLookAndFeel(qreal pixelDensity, int page)
{
    setPage(page);
    setDefaultFontSize(pixelDensity * 4);
}

void VCSpeedDial::render(QQuickView *view, QQuickItem *parent)
{
    if (view == nullptr || parent == nullptr)
        return;

    QQmlComponent *component = new QQmlComponent(view->engine(), QUrl("qrc:/VCSpeedDialItem.qml"));

    if (component->isError())
    {
        qDebug() << component->errors();
        delete component;
        return;
    }

    m_item = qobject_cast<QQuickItem*>(component->create());

    m_item->setParentItem(parent);
    m_item->setProperty("speedObj", QVariant::fromValue(this));
}

QString VCSpeedDial::propertiesResource() const
{
    return QString("qrc:/VCSpeedDialProperties.qml");
}

QString VCSpeedDial::presetsResource() const
{
    return QString("qrc:/VCSpeedDialPresets.qml");
}

bool VCSpeedDial::supportsPresets() const
{
    return true;
}

VCWidget *VCSpeedDial::createCopy(VCWidget *parent) const
{
    Q_ASSERT(parent != nullptr);

    VCSpeedDial *speedDial = new VCSpeedDial(m_doc, parent);
    if (speedDial->copyFrom(this) == false)
    {
        delete speedDial;
        speedDial = nullptr;
    }

    return speedDial;
}

bool VCSpeedDial::copyFrom(const VCWidget *widget)
{
    const VCSpeedDial *speedDial = qobject_cast<const VCSpeedDial*> (widget);
    if (speedDial == nullptr)
        return false;

    /* Copy and set properties */
    setVisibilityMask(speedDial->visibilityMask());
    setCurrentTime(speedDial->currentTime());
    setTimeMinimumValue(speedDial->timeMinimumValue());
    setTimeMaximumValue(speedDial->timeMaximumValue());

    setFunctions(speedDial->functions());

    clearPresets();
    for (VCSpeedDialPreset *preset : speedDial->presets())
        addPresetInternal(new VCSpeedDialPreset(*preset));
    m_lastAssignedPresetId = speedDial->m_lastAssignedPresetId;
    emit presetsListChanged();

    /* Common stuff */
    return VCWidget::copyFrom(widget);
}

void VCSpeedDial::cacheMultipliers()
{
    m_multiplierCache << 0; // None
    m_multiplierCache << 0; // Zero
    m_multiplierCache << 1000 / 16;
    m_multiplierCache << 1000 / 8;
    m_multiplierCache << 1000 / 4;
    m_multiplierCache << 1000 / 2;
    m_multiplierCache << 1000;
    m_multiplierCache << 1000 * 2;
    m_multiplierCache << 1000 * 4;
    m_multiplierCache << 1000 * 8;
    m_multiplierCache << 1000 * 16;
}

/*********************************************************************
 * UI elements visibility
 *********************************************************************/

quint32 VCSpeedDial::visibilityMask() const
{
    return m_visibilityMask;
}

void VCSpeedDial::setVisibilityMask(quint32 mask)
{
    if (mask == m_visibilityMask)
        return;

    m_visibilityMask = mask;
    emit visibilityMaskChanged();
}

/*********************************************************************
 * Dial absolute time
 *********************************************************************/

uint VCSpeedDial::timeMinimumValue() const
{
    return m_timeMinimumValue;
}

void VCSpeedDial::setTimeMinimumValue(uint newTimeMinimumValue)
{
    if (m_timeMinimumValue == newTimeMinimumValue)
        return;

    m_timeMinimumValue = newTimeMinimumValue;
    emit timeMinimumValueChanged();
}

uint VCSpeedDial::timeMaximumValue() const
{
    return m_timeMaximumValue;
}

void VCSpeedDial::setTimeMaximumValue(uint newTimeMaximumValue)
{
    if (m_timeMaximumValue == newTimeMaximumValue)
        return;

    m_timeMaximumValue = newTimeMaximumValue;
    emit timeMaximumValueChanged();
}

uint VCSpeedDial::currentTime() const
{
    return m_currentTime;
}

void VCSpeedDial::setCurrentTime(uint newCurrentTime)
{
    if (m_currentTime == newCurrentTime)
        return;

    Tardis::instance()->enqueueAction(Tardis::VCSpeedDialSetTime, id(), m_currentTime, newCurrentTime);

    m_currentTime = newCurrentTime;

    if (m_currentTime != 0)
        applyFunctionsTime();

    emit currentTimeChanged();
}

bool VCSpeedDial::resetOnDialChange() const
{
    return m_resetOnDialChange;
}

void VCSpeedDial::setResetOnDialChange(bool newResetOnDialChange)
{
    if (m_resetOnDialChange == newResetOnDialChange)
        return;

    m_resetOnDialChange = newResetOnDialChange;
    emit resetOnDialChangeChanged();
}

/*********************************************************************
 * Speed factor
 *********************************************************************/

VCSpeedDial::SpeedMultiplier VCSpeedDial::currentFactor() const
{
    return m_currentFactor;
}

void VCSpeedDial::setCurrentFactor(SpeedMultiplier factor)
{
    if (factor == m_currentFactor)
        return;

    Tardis::instance()->enqueueAction(Tardis::VCSpeedDialSetFactor, id(), int(m_currentFactor), int(factor));

    m_currentFactor = factor;

    applyFunctionsTime();

    emit currentFactorChanged();
}

void VCSpeedDial::increaseSpeedFactor()
{
    if (m_currentFactor < Sixteen)
    {
        int factor = m_currentFactor;
        factor++;
        setCurrentFactor(SpeedMultiplier(factor));
    }
}

void VCSpeedDial::decreaseSpeedFactor()
{
    if (m_currentFactor > OneSixteenth)
    {
        int factor = m_currentFactor;
        factor--;
        setCurrentFactor(SpeedMultiplier(factor));
    }
}

/*********************************************************************
 * Functions
 *********************************************************************/

QMap<quint32, VCSpeedDial::VCSpeedDialFunction> VCSpeedDial::functions() const
{
    return m_functions;
}

void VCSpeedDial::setFunctions(const QMap<quint32, VCSpeedDialFunction> &functions)
{
    m_functions = functions;
}

void VCSpeedDial::addFunction(VCSpeedDialFunction function)
{
    m_functions[function.m_fId] = function;

    emit functionsListChanged();
}

void VCSpeedDial::addFunction(quint32 functionID)
{
    if (m_functions.contains(functionID))
        return;

    VCSpeedDialFunction func;
    func.m_fId = functionID;
    func.m_fadeInFactor = None;
    func.m_fadeOutFactor = None;
    func.m_durationFactor = One;

    m_functions[functionID] = func;

    emit functionsListChanged();
}

void VCSpeedDial::removeFunction(quint32 functionID)
{
    m_functions.remove(functionID);

    emit functionsListChanged();
}

QVariant VCSpeedDial::functionsList()
{
    QVariantList fList;

    for (VCSpeedDialFunction &func : m_functions)
    {
        QVariantMap fMap;
        fMap.insert("funcID", func.m_fId);
        fMap.insert("fadeIn", func.m_fadeInFactor);
        fMap.insert("fadeOut", func.m_fadeOutFactor);
        fMap.insert("duration", func.m_durationFactor);
        fList.append(fMap);
    }

    return QVariant::fromValue(fList);
}

/*********************************************************************
 * Presets
 *********************************************************************/

QVariantList VCSpeedDial::presetsList() const
{
    QVariantList list;
    for (const VCSpeedDialPreset *preset : presets())
    {
        QVariantMap entry;
        entry.insert("id", preset->m_id);
        entry.insert("name", preset->m_name);
        entry.insert("value", preset->m_value);
        list.append(entry);
    }
    return list;
}

int VCSpeedDial::addPreset(QString name, int value)
{
    quint8 newId = ++m_lastAssignedPresetId;
    VCSpeedDialPreset *preset = new VCSpeedDialPreset(newId);
    preset->m_name = name;
    preset->m_value = value;
    addPresetInternal(preset);
    emit presetsListChanged();
    return newId;
}

void VCSpeedDial::removePreset(quint8 presetId)
{
    for (int i = 0; i < m_presets.count(); ++i)
    {
        if (m_presets.at(i)->m_id == presetId)
        {
            if (presetId <= UCHAR_MAX - INPUT_PRESETS_BASE_ID)
                unregisterExternalControl(INPUT_PRESETS_BASE_ID + presetId);
            delete m_presets.takeAt(i);
            emit presetsListChanged();
            return;
        }
    }
}

void VCSpeedDial::setPresetName(quint8 presetId, QString name)
{
    VCSpeedDialPreset *preset = findPreset(presetId);
    if (preset == nullptr)
        return;

    if (preset->m_name == name)
        return;

    preset->m_name = name;
    if (presetId <= UCHAR_MAX - INPUT_PRESETS_BASE_ID)
    {
        unregisterExternalControl(INPUT_PRESETS_BASE_ID + presetId);
        registerExternalControl(INPUT_PRESETS_BASE_ID + presetId,
                                tr("Preset: %1").arg(preset->m_name), true);
    }
    emit presetsListChanged();
}

void VCSpeedDial::setPresetValue(quint8 presetId, int value)
{
    VCSpeedDialPreset *preset = findPreset(presetId);
    if (preset == nullptr)
        return;

    if (preset->m_value == value)
        return;

    preset->m_value = value;
    emit presetsListChanged();
}

QList<VCSpeedDialPreset*> VCSpeedDial::presets() const
{
    QList<VCSpeedDialPreset*> list = m_presets;
    std::sort(list.begin(), list.end(), VCSpeedDialPreset::compare);
    return list;
}

void VCSpeedDial::clearPresets()
{
    for (VCSpeedDialPreset *preset : m_presets)
    {
        if (preset->m_id <= UCHAR_MAX - INPUT_PRESETS_BASE_ID)
            unregisterExternalControl(INPUT_PRESETS_BASE_ID + preset->m_id);
    }
    qDeleteAll(m_presets);
    m_presets.clear();
}

VCSpeedDialPreset *VCSpeedDial::findPreset(quint8 presetId) const
{
    for (VCSpeedDialPreset *preset : m_presets)
    {
        if (preset->m_id == presetId)
            return preset;
    }
    return nullptr;
}

void VCSpeedDial::addPresetInternal(VCSpeedDialPreset *preset)
{
    if (preset == nullptr)
        return;

    m_presets.append(preset);
    if (preset->m_id > m_lastAssignedPresetId)
        m_lastAssignedPresetId = preset->m_id;

    if (preset->m_id <= UCHAR_MAX - INPUT_PRESETS_BASE_ID)
    {
        registerExternalControl(INPUT_PRESETS_BASE_ID + preset->m_id,
                                tr("Preset: %1").arg(preset->m_name), true);
    }
}

void VCSpeedDial::setFunctionSpeed(quint32 fid, int speedType, SpeedMultiplier amount)
{
    VCSpeedDialFunction func = m_functions[fid];

    switch (speedType)
    {
        case Function::FadeIn: func.m_fadeInFactor = amount; break;
        case Function::FadeOut: func.m_fadeOutFactor = amount; break;
        case Function::Duration: func.m_durationFactor = amount; break;
    }

    m_functions[fid] = func;
}

void VCSpeedDial::applyFunctionsTime()
{
    float factoredTime = m_currentTime * (m_multiplierCache[m_currentFactor] / 1000.0);

    for (const VCSpeedDialFunction &func : m_functions)
    {
        Function *function = m_doc->function(func.m_fId);
        if (function != NULL)
        {
            if (func.m_fadeInFactor != VCSpeedDial::None)
                function->setFadeInSpeed(factoredTime * (m_multiplierCache[func.m_fadeInFactor] / 1000.0));

            if (func.m_fadeOutFactor != VCSpeedDial::None)
                function->setFadeOutSpeed(factoredTime * (m_multiplierCache[func.m_fadeOutFactor] / 1000.0));

            if (func.m_durationFactor != VCSpeedDial::None)
                function->setDuration(factoredTime * (m_multiplierCache[func.m_durationFactor] / 1000.0));
        }
    }
}

/*********************************************************************
 * Tap
 *********************************************************************/

int VCSpeedDial::tapTimeValue() const
{
    return m_tapTimeValue;
}

void VCSpeedDial::tap()
{
    qint64 currTime = QDateTime::currentMSecsSinceEpoch();

    if (m_lastTap != 0 && currTime - m_lastTap < 1500)
    {
        int newTime = static_cast<int>(currTime - m_lastTap);

        m_tapHistory.append(newTime);

        int tapTime = calculateBPMByTapIntervals(m_tapHistory);

        setCurrentTime(tapTime);

        if (m_tapTimeValue != tapTime)
        {
            m_tapTimeValue = tapTime;
            emit tapTimeValueChanged();
        }
    }
    else
    {
        m_lastTap = 0;
        m_tapHistory.clear();
    }
    m_lastTap = currTime;
}

void VCSpeedDial::resetTap()
{
    m_lastTap = 0;
    m_tapHistory.clear();

    if (m_tapTimeValue != 0)
    {
        m_tapTimeValue = 0;
        emit tapTimeValueChanged();
    }
}

int VCSpeedDial::calculateBPMByTapIntervals(QList<int> &tapHistory)
{
    // reduce size to only 16 taps
    while (tapHistory.size() > 16)
        tapHistory.removeFirst();

    // copy and sort to find median
    QList<int> sorted = tapHistory;
    std::sort(sorted.begin(), sorted.end());

    int tapHistoryMedian = sorted[sorted.size() / 2];

    double n = 1, tapx = 0, tapy = 0;
    double sum_x = 0, sum_y = 0, sum_xx = 0, sum_xy = 0;

    for (int i = 0; i < tapHistory.size(); i++)
    {
        int intervalMs = tapHistory[i];
        n++;
        // Divide by tapHistoryMedian to determine if a tap was skipped during input
        tapx += qFloor((tapHistoryMedian / 2.0 + intervalMs) / tapHistoryMedian);
        tapy += intervalMs;
        sum_x += tapx;
        sum_y += tapy;
        sum_xx += tapx * tapx;
        sum_xy += tapx * tapy;
    }

    double denom = n * sum_xx - sum_x * sum_x;
    if (qFuzzyIsNull(denom))
        return tapHistory.last();

    return static_cast<int>((n * sum_xy - sum_x * sum_y) / denom);
}

/*********************************************************************
 * External input
 *********************************************************************/

void VCSpeedDial::updateFeedback()
{
    // TODO: DIAL_ID, TAP_ID(?)
    sendFeedback(m_currentFactor == OneSixteenth ? UCHAR_MAX : 0, INPUT_1_16X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == OneEighth ? UCHAR_MAX : 0, INPUT_1_8X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == OneFourth ? UCHAR_MAX : 0, INPUT_1_4X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == Half ? UCHAR_MAX : 0, INPUT_1_2X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == Two ? UCHAR_MAX : 0, INPUT_2X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == Four ? UCHAR_MAX : 0, INPUT_4X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == Eight ? UCHAR_MAX : 0, INPUT_8X_ID, VCWidget::ExactValue);
    sendFeedback(m_currentFactor == Sixteen ? UCHAR_MAX : 0, INPUT_16X_ID, VCWidget::ExactValue);
}

void VCSpeedDial::slotInputValueChanged(quint8 id, uchar value)
{
    // filter unwanted values
    if (id != INPUT_DIAL_ID && value != UCHAR_MAX)
        return;

    if (id >= INPUT_PRESETS_BASE_ID)
    {
        quint8 presetId = id - INPUT_PRESETS_BASE_ID;
        VCSpeedDialPreset *preset = findPreset(presetId);
        if (preset != nullptr)
            setCurrentTime(preset->m_value);
        return;
    }

    switch (id)
    {
        case INPUT_DIAL_ID:
        {
            int ms = static_cast<int> (SCALE(qreal(value), qreal(0), qreal(255),
                                        qreal(timeMinimumValue()),
                                        qreal(timeMaximumValue())));
            setCurrentTime(ms);
        }
        break;
        case INPUT_TAP_ID:
            tap();
        break;
        case INPUT_MULT_ID:
            increaseSpeedFactor();
        break;
        case INPUT_DIV_ID:
            decreaseSpeedFactor();
        break;
        case INPUT_RESET_ID:
            setCurrentFactor(One);
        break;
        case INPUT_APPLY_ID:
            applyFunctionsTime();
        break;
        case INPUT_1_16X_ID:
            setCurrentFactor(OneSixteenth);
        break;
        case INPUT_1_8X_ID:
            setCurrentFactor(OneEighth);
        break;
        case INPUT_1_4X_ID:
            setCurrentFactor(OneFourth);
        break;
        case INPUT_1_2X_ID:
            setCurrentFactor(Half);
        break;
        case INPUT_2X_ID:
            setCurrentFactor(Two);
        break;
        case INPUT_4X_ID:
            setCurrentFactor(Four);
        break;
        case INPUT_8X_ID:
            setCurrentFactor(Eight);
        break;
        case INPUT_16X_ID:
            setCurrentFactor(Sixteen);
        break;
    }
}

/*********************************************************************
 * Load & Save
 *********************************************************************/

bool VCSpeedDial::loadXML(QXmlStreamReader &root)
{
    clearPresets();
    m_lastAssignedPresetId = 15;

    if (root.name() != KXMLQLCVCSpeedDial)
    {
        qWarning() << Q_FUNC_INFO << "Speed dial node not found";
        return false;
    }

    QXmlStreamAttributes attrs = root.attributes();

    /* Widget commons */
    loadXMLCommon(root);

    while (root.readNextStartElement())
    {
        if (root.name() == KXMLQLCWindowState)
        {
            bool visible = false;
            int x = 0, y = 0, w = 0, h = 0;
            loadXMLWindowState(root, &x, &y, &w, &h, &visible);
            setGeometry(QRect(x, y, w, h));
        }
        else if (root.name() == KXMLQLCVCWidgetAppearance)
        {
            loadXMLAppearance(root);
        }
        else if (root.name() == KXMLQLCVCSpeedDialVisibilityMask)
        {
            setVisibilityMask(root.readElementText().toUInt());
        }
        else if (root.name() == KXMLQLCVCSpeedDialAbsoluteValue)
        {
            // Value range
            QXmlStreamAttributes vAttrs = root.attributes();
            if (vAttrs.hasAttribute(KXMLQLCVCSpeedDialAbsoluteValueMin) &&
                vAttrs.hasAttribute(KXMLQLCVCSpeedDialAbsoluteValueMax))
            {
                setTimeMinimumValue(vAttrs.value(KXMLQLCVCSpeedDialAbsoluteValueMin).toString().toUInt());
                setTimeMaximumValue(vAttrs.value(KXMLQLCVCSpeedDialAbsoluteValueMax).toString().toUInt());
            }
            loadXMLSources(root, INPUT_DIAL_ID);
        }
        else if (root.name() == KXMLQLCVCSpeedDialTime)
        {
            setCurrentTime(root.readElementText().toUInt());
        }
        else if (root.name() == KXMLQLCVCSpeedDialResetFactorOnDialChange)
        {
            setResetOnDialChange(root.readElementText() == KXMLQLCTrue);
        }
        else if (root.name() == KXMLQLCVCSpeedDialFunction)
        {
            QXmlStreamAttributes attrs = root.attributes();
            QString text = root.readElementText();
            if (text.isEmpty())
            {
                qWarning() << Q_FUNC_INFO << "Function ID not found";
                root.skipCurrentElement();
            }
            VCSpeedDialFunction func;
            func.m_fId = text.toUInt();
            func.m_fadeInFactor = None;
            func.m_fadeOutFactor = None;
            func.m_durationFactor = One;

            // check for attribute presence
            if (attrs.hasAttribute(KXMLQLCFunctionSpeedFadeIn) == true)
                func.m_fadeInFactor = SpeedMultiplier(attrs.value(KXMLQLCFunctionSpeedFadeIn).toString().toUInt());
            if (attrs.hasAttribute(KXMLQLCFunctionSpeedFadeOut) == true)
                func.m_fadeOutFactor = SpeedMultiplier(attrs.value(KXMLQLCFunctionSpeedFadeOut).toString().toUInt());
            if (attrs.hasAttribute(KXMLQLCFunctionSpeedDuration) == true)
                func.m_durationFactor = SpeedMultiplier(attrs.value(KXMLQLCFunctionSpeedDuration).toString().toUInt());

            addFunction(func);
        }
        else if (root.name() == KXMLQLCVCSpeedDialPreset)
        {
            VCSpeedDialPreset *preset = new VCSpeedDialPreset(0xff);
            if (preset->loadXML(root))
                addPresetInternal(preset);
            else
                delete preset;
        }
        else if (root.name() == KXMLQLCVCWidgetInput)
        {
            loadXMLInputSource(root, VCWIDGET_AUTODETECT_INPUT_ID);
        }
        else
        {
            qWarning() << Q_FUNC_INFO << "Unknown speed dial tag:" << root.name().toString();
            root.skipCurrentElement();
        }
    }

    emit presetsListChanged();
    return true;
}

bool VCSpeedDial::saveXML(QXmlStreamWriter *doc) const
{
    Q_ASSERT(doc != nullptr);

    /* VC object entry */
    doc->writeStartElement(KXMLQLCVCSpeedDial);

    saveXMLCommon(doc);

    /* Window state */
    saveXMLWindowState(doc);

    /* Appearance */
    saveXMLAppearance(doc);

    /* Visibility bitmask */
    doc->writeTextElement(KXMLQLCVCSpeedDialVisibilityMask, QString::number(m_visibilityMask));

    /* Save time */
    doc->writeTextElement(KXMLQLCVCSpeedDialTime, QString::number(currentTime()));

    /* Reset factor on dial change */
    if (resetOnDialChange())
        doc->writeTextElement(KXMLQLCVCSpeedDialResetFactorOnDialChange, KXMLQLCTrue);

    /* Absolute input */
    doc->writeStartElement(KXMLQLCVCSpeedDialAbsoluteValue);
    doc->writeAttribute(KXMLQLCVCSpeedDialAbsoluteValueMin, QString::number(timeMinimumValue()));
    doc->writeAttribute(KXMLQLCVCSpeedDialAbsoluteValueMax, QString::number(timeMaximumValue()));
    saveXMLInputControl(doc, INPUT_DIAL_ID);
    doc->writeEndElement();

    for (const VCSpeedDialFunction &func : m_functions)
    {
        /* Function tag */
        doc->writeStartElement(KXMLQLCVCSpeedDialFunction);

        /* Multipliers */
        doc->writeAttribute(KXMLQLCFunctionSpeedFadeIn, QString::number(func.m_fadeInFactor));
        doc->writeAttribute(KXMLQLCFunctionSpeedFadeOut, QString::number(func.m_fadeOutFactor));
        doc->writeAttribute(KXMLQLCFunctionSpeedDuration, QString::number(func.m_durationFactor));

        /* Function ID */
        doc->writeCharacters(QString::number(func.m_fId));

        /* Close the <Function> tag */
        doc->writeEndElement();
    }

    for (quint8 iId = INPUT_TAP_ID; iId <= INPUT_16X_ID; iId++)
        saveXMLInputControl(doc, iId);

    for (VCSpeedDialPreset *preset : presets())
        preset->saveXML(doc);

    /* Write the <end> tag */
    doc->writeEndElement();

    return true;
}
