///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020-2023 Jon Beniston, M7RCE <jon@beniston.com>                //
// Copyright (C) 2020-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com>          //
//                                                                               //
// This program is free software; you can redistribute it and/or modify          //
// it under the terms of the GNU General Public License as published by          //
// the Free Software Foundation as version 3 of the License, or                  //
// (at your option) any later version.                                           //
//                                                                               //
// This program is distributed in the hope that it will be useful,               //
// but WITHOUT ANY WARRANTY; without even the implied warranty of                //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
// GNU General Public License V3 for more details.                               //
//                                                                               //
// You should have received a copy of the GNU General Public License             //
// along with this program. If not, see <http://www.gnu.org/licenses/>.          //
///////////////////////////////////////////////////////////////////////////////////

#include <QJsonArray>
#include <QList>

#include "webapiutils.h"

const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
    {"sdrangel.channel.adsbdemod", "ADSBDemodSettings"},
    {"sdrangel.channel.modais", "AISModSettings"},
    {"sdrangel.channel.aisdemod", "AISDemodSettings"},
    {"sdrangel.channel.amdemod", "AMDemodSettings"},
    {"sdrangel.channel.aptdemod", "APTDemodSettings"},
    {"de.maintech.sdrangelove.channel.am", "AMDemodSettings"}, // remap
    {"sdrangel.channeltx.modam", "AMModSettings"},
    {"sdrangel.channeltx.modatv", "ATVModSettings"},
    {"sdrangel.channeltx.moddatv", "DATVModSettings"},
    {"sdrangel.channel.bfm", "BFMDemodSettings"},
    {"sdrangel.channel.chanalyzer", "ChannelAnalyzerSettings"},
    {"sdrangel.channel.channelpower", "ChannelPowerSettings"},
    {"sdrangel.channel.chanalyzerng", "ChannelAnalyzerSettings"}, // remap
    {"org.f4exb.sdrangelove.channel.chanalyzer", "ChannelAnalyzerSettings"}, // remap
    {"sdrangel.channel.chirpchatdemod", "ChirpChatDemodSettings"},
    {"sdrangel.channel.meshtasticdemod", "ChirpChatDemodSettings"}, // alias: Meshtastic uses ChirpChatDemodSettings schema
    {"sdrangel.channel.modchirpchat", "ChirpChatModSettings"},
    {"sdrangel.channel.demodatv", "ATVDemodSettings"},
    {"sdrangel.channel.demoddatv", "DATVDemodSettings"},
    {"sdrangel.channel.dabdemod", "DABDemodSettings"},
    {"sdrangel.channel.doa2", "DOA2Settings"},
    {"sdrangel.channel.dscdemod", "DSCDemodSettings"},
    {"sdrangel.channel.dsddemod", "DSDDemodSettings"},
    {"sdrangel.channel.endoftraindemod", "EndOfTrainDemodSettings"},
    {"sdrangel.channel.filesink", "FileSinkSettings"},
    {"sdrangel.channeltx.filesource", "FileSourceSettings"},
    {"sdrangel.channel.freedvdemod", "FreeDVDemodSettings"},
    {"sdrangel.channeltx.freedvmod", "FreeDVModSettings"},
    {"sdrangel.channel.freqscanner", "FreqScannerSettings"},
    {"sdrangel.channel.freqtracker", "FreqTrackerSettings"},
    {"sdrangel.channel.heatmap", "HeatMapSettings"},
    {"sdrangel.channel.ilsdemod", "ILSDemodSettings"},
    {"sdrangel.channel.inmarsatdemod", "InmarsatDemodSettings"},
    {"sdrangel.channel.navtexemod", "NavtexDemodSettings"},
    {"sdrangel.channel.m17demod", "M17DemodSettings"},
    {"sdrangel.channeltx.modm17", "M17ModSettings"},
    {"sdrangel.channel.nfmdemod", "NFMDemodSettings"},
    {"de.maintech.sdrangelove.channel.nfm", "NFMDemodSettings"}, // remap
    {"sdrangel.channeltx.modnfm", "NFMModSettings"},
    {"sdrangel.channel.noisefigure", "NoiseFigureSettings"},
    {"sdrangel.demod.localsink", "LocalSinkSettings"},
    {"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap
    {"sdrangel.channel.localsource", "LocalSourceSettings"},
    {"sdrangel.channel.packetdemod", "PacketDemodSettings"},
    {"sdrangel.channel.pagerdemod", "PagerDemodSettings"},
    {"sdrangel.channeltx.modpacket", "PacketModSettings"},
    {"sdrangel.channel.psk31mod", "PSK31ModSettings"},
    {"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"},
    {"sdrangel.channel.radioclock", "RadioClockSettings"},
    {"sdrangel.channel.radiosondedemod", "RadiosondeDemodSettings"},
    {"sdrangel.demod.remotesink", "RemoteSinkSettings"},
    {"sdrangel.demod.remotetcpsink", "RemoteTCPSinkSettings"},
    {"sdrangel.channeltx.remotesource", "RemoteSourceSettings"},
    {"sdrangel.channel.rttydemod", "RTTYDemodSettings"},
    {"sdrangel.channel.rttymod", "RTTYModSettings"},
    {"sdrangel.channeltx.modssb", "SSBModSettings"},
    {"sdrangel.channel.ssbdemod", "SSBDemodSettings"},
    {"sdrangel.channel.wdsprx", "WDSPRxSettings"},
    {"sdrangel.channel.ft8demod", "FT8DemodSettings"},
    {"de.maintech.sdrangelove.channel.ssb", "SSBDemodSettings"}, // remap
    {"sdrangel.channel.radioastronomy", "RadioAstronomySettings"},
    {"sdrangel.channeltx.udpsource", "UDPSourceSettings"},
    {"sdrangel.channeltx.udpsink", "UDPSinkSettings"}, // remap
    {"sdrangel.channel.udpsink", "UDPSinkSettings"},
    {"sdrangel.channel.udpsrc", "UDPSourceSettings"}, // remap
    {"sdrangel.channel.vordemodmc", "VORDemodMCSettings"},
    {"sdrangel.channel.vordemod", "VORDemodSettings"},
    {"sdrangel.channel.wfmdemod", "WFMDemodSettings"},
    {"de.maintech.sdrangelove.channel.wfm", "WFMDemodSettings"}, // remap
    {"sdrangel.channeltx.modwfm", "WFMModSettings"},
    {"sdrangel.channel.beamsteeringcwmod", "BeamSteeringCWModSettings"},
    {"sdrangel.channelmimo.interferometer", "InterferometerSettings"},
    {"sdrangel.channel.sigmffilesink", "SigMFFileSinkSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_deviceIdToSettingsKey = {
    {"sdrangel.samplesource.aaroniartsasource", "aaroniaRTSASettings"},
    {"sdrangel.samplesink.aaroniartsaoutput", "aaroniaRTSAOutputSettings"},
    {"sdrangel.samplesource.airspy", "airspySettings"},
    {"sdrangel.samplesource.airspyhf", "airspyHFSettings"},
    {"sdrangel.samplesource.androidsdrdriverinput", "androidSDRDriverInputSettings"},
    {"sdrangel.samplemimo.audiocatsiso", "audioCATSISOSettings"},
    {"sdrangel.samplesource.audioinput", "audioInputSettings"},
    {"sdrangel.samplesink.audiooutput", "audioOutputSettings"},
    {"sdrangel.samplesource.bladerf1input", "bladeRF1InputSettings"},
    {"sdrangel.samplesource.bladerf", "bladeRF1InputSettings"}, // remap
    {"sdrangel.samplesink.bladerf1output", "bladeRF1OutputSettings"},
    {"sdrangel.samplesource.bladerf1output", "bladeRF1OutputSettings"}, // remap
    {"sdrangel.samplesource.bladerfoutput", "bladeRF1OutputSettings"}, // remap
    {"sdrangel.samplesource.bladerf2input", "bladeRF2InputSettings"},
    {"sdrangel.samplesink.bladerf2output", "bladeRF2OutputSettings"},
    {"sdrangel.samplesource.bladerf2output", "bladeRF2OutputSettings"}, // remap
    {"sdrangel.samplemimo.bladerf2mimo", "bladeRF2MIMOSettings"},
    {"sdrangel.samplesource.fcdpro", "fcdProSettings"},
    {"sdrangel.samplesource.fcdproplus", "fcdProPlusSettings"},
    {"sdrangel.samplesource.fileinput", "fileInputSettings"},
    {"sdrangel.samplesource.filesource", "fileInputSettings"}, // remap
    {"sdrangel.samplesource.hackrf", "hackRFInputSettings"},
    {"sdrangel.samplesink.hackrf", "hackRFOutputSettings"},
    {"sdrangel.samplesource.hackrfoutput", "hackRFOutputSettings"}, // remap
    {"sdrangel.samplesource.kiwisdrsource", "kiwiSDRSettings"},
    {"sdrangel.samplesource.limesdr", "limeSdrInputSettings"},
    {"sdrangel.samplesink.limesdr", "limeSdrOutputSettings"},
    {"sdrangel.samplesource.localinput", "localInputSettings"},
    {"sdrangel.samplesink.localoutput", "localOutputSettings"},
    {"sdrangel.samplesource.localoutput", "localOutputSettings"}, // remap
    {"sdrangel.samplemimo.metismiso", "metisMISOSettings"},
    {"sdrangel.samplesource.perseus", "perseusSettings"},
    {"sdrangel.samplesource.plutosdr", "plutoSdrInputSettings"},
    {"sdrangel.samplesink.plutosdr", "plutoSdrOutputSettings"},
    {"sdrangel.samplesource.rtlsdr", "rtlSdrSettings"},
    {"sdrangel.samplesource.remoteinput", "remoteInputSettings"},
    {"sdrangel.samplesource.remotetcpinput", "remoteTCPInputSettings"},
    {"sdrangel.samplesink.remoteoutput", "remoteOutputSettings"},
    {"sdrangel.samplesource.sdrplay", "sdrPlaySettings"},
    {"sdrangel.samplesource.sdrplayv3", "sdrPlayV3Settings"},
    {"sdrangel.samplesource.sigmffileinput", "sigMFFileInputSettings"},
    {"sdrangel.samplesource.soapysdrinput", "soapySDRInputSettings"},
    {"sdrangel.samplesink.soapysdroutput", "soapySDROutputSettings"},
    {"sdrangel.samplesource.testsource", "testSourceSettings"},
    {"sdrangel.samplemimo.testmi", "testMISettings"},
    {"sdrangel.samplemimo.testmosync", "testMOSyncSettings"},
    {"sdrangel.samplesource.usrp", "usrpInputSettings"},
    {"sdrangel.samplesink.usrp", "usrpOutputSettings"},
    {"sdrangel.samplesource.xtrx", "xtrxInputSettings"},
    {"sdrangel.samplesink.xtrx", "xtrxOutputSettings"},
    {"sdrangel.samplemimo.xtrxmimo", "xtrxMIMOSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
    {"ADSBDemod", "ADSBDemodSettings"},
    {"AISDemod", "AISDemodSettings"},
    {"AISMod", "AISModSettings"},
    {"APTDemod", "APTDemodSettings"},
    {"AMDemod", "AMDemodSettings"},
    {"AMMod", "AMModSettings"},
    {"ATVDemod", "ATVDemodSettings"},
    {"ATVMod", "ATVModSettings"},
    {"BFMDemod", "BFMDemodSettings"},
    {"ChannelAnalyzer", "ChannelAnalyzerSettings"},
    {"ChirpChatDemod", "ChirpChatDemodSettings"},
    {"MeshtasticDemod", "ChirpChatDemodSettings"}, // alias: Meshtastic uses ChirpChatDemodSettings schema
    {"ChirpChatMod", "ChirpChatModSettings"},
    {"ChannelPower", "ChannelPowerSettings"},
    {"DATVDemod", "DATVDemodSettings"},
    {"DATVMod", "DATVModSettings"},
    {"DABDemod", "DABDemodSettings"},
    {"DOA2", "DOA2Settings"},
    {"DSCDemod", "DSCDemodSettings"},
    {"DSDDemod", "DSDDemodSettings"},
    {"EndOfTrainDemod", "EndOfTrainDemodSettings"},
    {"FileSink", "FileSinkSettings"},
    {"FileSource", "FileSourceSettings"},
    {"FreeDVDemod", "FreeDVDemodSettings"},
    {"FreeDVMod", "FreeDVModSettings"},
    {"FreqScanner", "FreqScannerSettings"},
    {"FreqTracker", "FreqTrackerSettings"},
    {"HeatMap", "HeatMapSettings"},
    {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModSettings"},
    {"M17Demod", "M17DemodSettings"},
    {"M17Mod", "M17ModSettings"},
    {"NavtexDemod", "NavtexDemodSettings"},
    {"NFMDemod", "NFMDemodSettings"},
    {"NFMMod", "NFMModSettings"},
    {"NoiseFigure", "NoiseFigureSettings"},
    {"PacketDemod", "PacketDemodSettings"},
    {"PacketMod", "PacketModSettings"},
    {"PagerDemod", "PagerDemodSettings"},
    {"PSK31Mod", "PSK31ModSettings"},
    {"LocalSink", "LocalSinkSettings"},
    {"LocalSource", "LocalSourceSettings"},
    {"RadioAstronomy", "RadioAstronomySettings"},
    {"RadioClock", "RadioClockSettings"},
    {"RadiosondeDemod", "RadiosondeDemodSettings"},
    {"RemoteSink", "RemoteSinkSettings"},
    {"RemoteSource", "RemoteSourceSettings"},
    {"RemoteTCPSink", "RemoteTCPSinkSettings"},
    {"RTTYDemod", "RTTYDemodSettings"},
    {"RTTYMod", "RTTYModSettings"},
    {"SSBMod", "SSBModSettings"},
    {"SSBDemod", "SSBDemodSettings"},
    {"WDSPRx", "WDSPRxSettings"},
    {"FT8Demod", "FT8DemodSettings"},
    {"UDPSink", "UDPSinkSettings"},
    {"UDPSource", "UDPSourceSettings"},
    {"VORDemodMC", "VORDemodMCSettings"},
    {"VORDemod", "VORDemodSettings"},
    {"WFMDemod", "WFMDemodSettings"},
    {"WFMMod", "WFMModSettings"},
    {"BeamSteeringCWMod", "BeamSteeringCWModSettings"},
    {"Interferometer", "InterferometerSettings"},
    {"SigMFFileSink", "SigMFFileSinkSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_channelTypeToActionsKey = {
    {"AISMod", "AISModActions"},
    {"APTDemod", "APTDemodActions"},
    {"FileSink", "FileSinkActions"},
    {"FileSource", "FileSourceActions"},
    {"FreqScanner", "FreqScannerActions"},
    {"SigMFFileSink", "SigMFFileSinkActions"},
    {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModActions"},
    {"RadioAstronomy", "RadioAstronomyActions"},
    {"PacketMod", "PacketModActions"},
    {"PSK31Mod", "PSK31ModActions"},
    {"RTTYMod", "RTTYModActions"}
};

const QMap<QString, QString> WebAPIUtils::m_sourceDeviceHwIdToSettingsKey = {
    {"AaroniaRTSA", "aaroniaRTSASettings"},
    {"Airspy", "airspySettings"},
    {"AirspyHF", "airspyHFSettings"},
    {"AndroidSDRDriverInput", "androidSDRDriverInputSettings"},
    {"AudioInput", "audioInputSettings"},
    {"BladeRF1", "bladeRF1InputSettings"},
    {"BladeRF2", "bladeRF2InputSettings"},
    {"FCDPro", "fcdProSettings"},
    {"FCDPro+", "fcdProPlusSettings"},
    {"FileInput", "fileInputSettings"},
    {"HackRF", "hackRFInputSettings"},
    {"KiwiSDR", "kiwiSDRSettings"},
    {"LimeSDR", "limeSdrInputSettings"},
    {"LocalInput", "localInputSettings"},
    {"Perseus", "perseusSettings"},
    {"PlutoSDR", "plutoSdrInputSettings"},
    {"RTLSDR", "rtlSdrSettings"},
    {"RemoteInput", "remoteInputSettings"},
    {"RemoteTCPInput", "remoteTCPInputSettings"},
    {"SDRplay1", "sdrPlaySettings"},
    {"SDRplayV3", "sdrPlayV3Settings"},
    {"SigMFFileInput", "sigMFFileInputSettings"},
    {"SoapySDR", "soapySDRInputSettings"},
    {"TestSource", "testSourceSettings"},
    {"USRP", "usrpInputSettings"},
    {"XTRX", "xtrxInputSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_sourceDeviceHwIdToActionsKey = {
    {"Airspy", "airspyActions"},
    {"AirspyHF", "airspyHFActions"},
    {"AudioInput", "audioInputActions"},
    {"BladeRF1", "bladeRF1InputActions"},
    {"FCDPro", "fcdProActions"},
    {"FCDPro+", "fcdProPlusActions"},
    {"HackRF", "hackRFInputActions"},
    {"KiwiSDR", "kiwiSDRActions"},
    {"LimeSDR", "limeSdrInputActions"},
    {"LocalInput", "localInputActions"},
    {"Perseus", "perseusActions"},
    {"PlutoSDR", "plutoSdrInputActions"},
    {"RemoteInput", "remoteInputActions"},
    {"RTLSDR", "rtlSdrActions"},
    {"SDRplay1", "sdrPlayActions"},
    {"SDRplayV3", "sdrPlayV3Actions"},
    {"SigMFFileInput", "sigMFFileActions"},
    {"SoapySDR", "soapySDRInputActions"},
    {"TestSource", "testSourceActions"},
    {"USRP", "usrpSourceActions"},
    {"XTRX", "xtrxInputActions"}
};

const QMap<QString, QString> WebAPIUtils::m_sinkDeviceHwIdToSettingsKey = {
    {"AaroniaRTSAOutput", "aaroniaRTSAOutputSettings"},
    {"AudioOutput", "audioOutputSettings"},
    {"BladeRF1", "bladeRF1OutputSettings"},
    {"BladeRF2", "bladeRF2OutputSettings"},
    {"FileOutput", "fileOutputSettings"},
    {"HackRF", "hackRFOutputSettings"},
    {"LimeSDR", "limeSdrOutputSettings"},
    {"LocalOutput", "localOutputSettings"},
    {"PlutoSDR", "plutoSdrOutputSettings"},
    {"RemoteOutput", "remoteOutputSettings"},
    {"SoapySDR", "soapySDROutputSettings"},
    {"USRP", "usrpOutputSettings"},
    {"XTRX", "xtrxOutputSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_sinkDeviceHwIdToActionsKey = {
};

const QMap<QString, QString> WebAPIUtils::m_mimoDeviceHwIdToSettingsKey = {
    {"AudioCATSISO", "audioCATSISOSettings"},
    {"BladeRF2", "bladeRF2MIMOSettings"},
    {"MetisMISO", "metisMISOSettings"},
    {"TestMI", "testMISettings"},
    {"TestMOSync", "testMOSyncSettings"},
    {"XTRX", "xtrxMIMOSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_mimoDeviceHwIdToActionsKey = {
};

const QMap<QString, QString> WebAPIUtils::m_featureTypeToSettingsKey = {
    {"AFC", "AFCSettings"},
    {"AIS", "AISSettings"},
    {"AMBE", "AMBESettings"},
    {"AntennaTools", "AntennaToolsSettings"},
    {"APRS", "APRSSettings"},
    {"DemodAnalyzer", "DemodAnalyzerSettings"},
    {"FreqDisplay", "FreqDisplaySettings"},
    {"JogdialController", "JogdialControllerSettings"},
    {"GS232Controller", "GS232ControllerSettings"}, // a.k.a Rotator Controller
    {"LimeRFE", "LimeRFESettings"},
    {"Map", "MapSettings"},
    {"MorseDecoder", "MorseDecoderSettings"},
    {"PERTester", "PERTesterSettings"},
    {"Radiosonde", "RadiosondeSettings"},
    {"RigCtlServer", "RigCtlServerSettings"},
    {"SatelliteTracker", "SatelliteTrackerSettings"},
    {"SID", "SIDSettings"},
    {"SimplePTT", "SimplePTTSettings"},
    {"SkyMap", "SkyMapSettings"},
    {"StarTracker", "StarTrackerSettings"},
    {"VORLocalizer", "VORLocalizerSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_featureTypeToActionsKey = {
    {"AFC", "AFCActions"},
    {"AMBE", "AMBEActions"},
    {"GS232Controller", "GS232ControllerActions"},
    {"LimeRFE", "LimeRFEActions"},
    {"Map", "MapActions"},
    {"PERTester", "PERTesterActions"},
    {"RigCtlServer", "RigCtlServerActions"},
    {"SatelliteTracker", "SatelliteTrackerActions"},
    {"SimplePTT", "SimplePTTActions"},
    {"SkyMap", "SkyMapActions"},
    {"StarTracker", "StarTrackerActions"},
    {"VORLocalizer", "VORLocalizerActions"},
    {"DemodAnalyzer", "DemodAnalyzerActions"},
    {"MorseDecoder", "MorseDecoderActions"}
};

const QMap<QString, QString> WebAPIUtils::m_featureURIToSettingsKey = {
    {"sdrangel.feature.afc", "AFCSettings"},
    {"sdrangel.feature.ais", "AISSSettings"},
    {"sdrangel.feature.ambe", "AMBESSettings"},
    {"sdrangel.feature.antennatools", "AntennaToolsSettings"},
    {"sdrangel.feature.aprs", "APRSSettings"},
    {"sdrangel.feature.demodanalyzer", "DemodAnalyzerSettings"},
    {"sdrangel.feature.freqdisplay", "FreqDisplaySettings"},
    {"sdrangel.feature.jogdialcontroller", "JogdialControllerSettings"},
    {"sdrangel.feature.gs232controller", "GS232ControllerSettings"},
    {"sdrangel.feature.limerfe", "LimeRFESettings"},
    {"sdrangel.feature.map", "MapSettings"},
    {"sdrangel.feature.morsedecoder", "MorseDecoderSettings"},
    {"sdrangel.feature.pertester", "PERTesterSettings"},
    {"sdrangel.feature.radiosonde", "RadiosondeSettings"},
    {"sdrangel.feature.rigctlserver", "RigCtlServerSettings"},
    {"sdrangel.feature.satellitetracker", "SatelliteTrackerSettings"},
    {"sdrangel.feature.simpleptt", "SimplePTTSettings"},
    {"sdrangel.feature.skymap", "SkyMapSettings"},
    {"sdrangel.feature.startracker", "StarTrackerSettings"},
    {"sdrangel.feature.vorlocalizer", "VORLocalizerSettings"}
};

// Get integer value from within JSON object
bool WebAPIUtils::getObjectInt(const QJsonObject &json, const QString &key, int &value)
{
    if (json.contains(key))
    {
        value = json[key].toInt();
        return true;
    }

    return false;
}

// Get string value from within JSON object
bool WebAPIUtils::getObjectString(const QJsonObject &json, const QString &key, QString &value)
{
    if (json.contains(key))
    {
        value = json[key].toString();
        return true;
    }

    return false;
}

// Get a list of JSON objects from within JSON object
bool WebAPIUtils::getObjectObjects(const QJsonObject &json, const QString &key, QList<QJsonObject> &objects)
{
    bool processed = false;

    if (json.contains(key))
    {
        if (json[key].isArray())
        {
            QJsonArray a = json[key].toArray();

            for (QJsonArray::const_iterator  it = a.begin(); it != a.end(); it++)
            {
                if (it->isObject())
                {
                    objects.push_back(it->toObject());
                    processed = true;
                }
            }
        }
    }

    return processed;
}

// Get double value from within nested JSON object
bool WebAPIUtils::getSubObjectDouble(const QJsonObject &json, const QString &key, double &value)
{
    for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                value = subObject[key].toDouble();
                return true;
            }
        }
    }

    return false;
}

// Set double value within nested JSON object
bool WebAPIUtils::setSubObjectDouble(QJsonObject &json, const QString &key, double value)
{
    for (QJsonObject::iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                subObject[key] = value;
                it.value() = subObject;
                return true;
            }
        }
    }

    return false;
}

// Get integer value from within nested JSON object
bool WebAPIUtils::getSubObjectInt(const QJsonObject &json, const QString &key, int &value)
{
    for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                value = subObject[key].toInt();
                return true;
            }
        }
    }

    return false;
}

// Set integer value within nested JSON object
bool WebAPIUtils::setSubObjectInt(QJsonObject &json, const QString &key, int value)
{
    for (QJsonObject::iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                subObject[key] = value;
                it.value() = subObject;
                return true;
            }
        }
    }

    return false;
}

// Get string value from within nested JSON object
bool WebAPIUtils::getSubObjectString(const QJsonObject &json, const QString &key, QString &value)
{
    for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                value = subObject[key].toString();
                return true;
            }
        }
    }

    return false;
}

// Set string value within nested JSON object
bool WebAPIUtils::setSubObjectString(QJsonObject &json, const QString &key, const QString &value)
{
    for (QJsonObject::iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                subObject[key] = value;
                it.value() = subObject;
                return true;
            }
        }
    }

    return false;
}

// Get string list from within nested JSON object
bool WebAPIUtils::getSubObjectIntList(const QJsonObject &json, const QString &key, const QString &subKey, QList<int> &values)
{
    for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                QJsonValue value = subObject[key];
                if (value.isArray())
                {
                    QJsonArray array = value.toArray();
                    for (int i = 0; i < array.size(); i++)
                    {
                        QJsonObject element = array[i].toObject();
                        if (element.contains(subKey)) {
                            values.append(element[subKey].toInt());
                        }
                    }
                    return true;
                }
            }
        }
    }

    return false;
}

bool WebAPIUtils::hasSubObject(const QJsonObject &json, const QString &key)
{
    for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key)) {
                return true;
            }
        }
    }

    return false;
}

// Set value within nested JSON object
bool WebAPIUtils::setSubObject(QJsonObject &json, const QString &key, const QVariant &value)
{
    for (QJsonObject::iterator it = json.begin(); it != json.end(); it++)
    {
        QJsonValue jsonValue = it.value();

        if (jsonValue.isObject())
        {
            QJsonObject subObject = jsonValue.toObject();

            if (subObject.contains(key))
            {
                if (subObject[key].isString()) {
                    subObject[key] = value.toString();
                } else if (subObject[key].isDouble()) {
                    subObject[key] = value.toDouble();
                } else {
                    qDebug() << "WebAPIUtils::setSubObject: Unsupported type";
                }
                it.value() = subObject;
                return true;
            }
        }
    }

    return false;
}

// look for value in key=value
bool WebAPIUtils::extractValue(const QJsonObject &json, const QString &key, QJsonValue &value)
{
    // final
    if (json.contains(key))
    {
        value = json[key];
        return true;
    }
    else
    {
        for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
        {
            QJsonValue jsonValue = it.value();

            if (jsonValue.isObject())
            {
                if (extractValue(jsonValue.toObject(), key, value)) {
                    return true;
                }
            }
        }
    }

    return false;
}

// look for [...] in key=[...]
bool WebAPIUtils::extractArray(const QJsonObject &json, const QString &key, QJsonArray &value)
{
    // final
    if (json.contains(key))
    {
        if (json[key].isArray())
        {
            value = json[key].toArray();
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
        {
            QJsonValue jsonValue = it.value();

            if (jsonValue.isObject())
            {
                if (extractArray(jsonValue.toObject(), key, value)) {
                    return true;
                }
            }
        }
    }

    return false;
}

// look for {...} in key={...}
bool WebAPIUtils::extractObject(const QJsonObject &json, const QString &key, QJsonObject &value)
{
    // final
    if (json.contains(key))
    {
        if (json[key].isObject())
        {
            value = json[key].toObject();
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
        {
            QJsonValue jsonValue = it.value();

            if (jsonValue.isObject())
            {
                if (extractObject(jsonValue.toObject(), key, value)) {
                    return true;
                }
            }
        }
    }

    return false;
}

// set value in key=value
bool WebAPIUtils::setValue(const QJsonObject &json, const QString &key, const QJsonValue &value)
{
    // final
    if (json.contains(key))
    {
        json[key] = value;
        return true;
    }
    else
    {
        for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
        {
            QJsonValue jsonValue = it.value();

            if (jsonValue.isObject())
            {
                if (setValue(jsonValue.toObject(), key, value)) {
                    return true;
                }
            }
        }
    }

    return false;
}

// set [...] in key=[...]
bool WebAPIUtils::setArray(const QJsonObject &json, const QString &key, const QJsonArray &value)
{
    // final
    if (json.contains(key))
    {
        if (json[key].isArray())
        {
            json[key] = value;
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
        {
            QJsonValue jsonValue = it.value();

            if (jsonValue.isObject())
            {
                if (setArray(jsonValue.toObject(), key, value)) {
                    return true;
                }
            }
        }
    }

    return false;
}

// set {...} in key={...}
bool WebAPIUtils::setObject(const QJsonObject &json, const QString &key, const QJsonObject &value)
{
    // final
    if (json.contains(key))
    {
        if (json[key].isObject())
        {
            json[key] = value;
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        for (QJsonObject::const_iterator  it = json.begin(); it != json.end(); it++)
        {
            QJsonValue jsonValue = it.value();

            if (jsonValue.isObject())
            {
                if (setObject(jsonValue.toObject(), key, value)) {
                    return true;
                }
            }
        }
    }

    return false;
}
