///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE                                        //
// Copyright (C) 2020 Edouard Griffiths, F4EXB                                   //
//                                                                               //
// 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.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.chanalyzerng", "ChannelAnalyzerSettings"}, // remap
    {"org.f4exb.sdrangelove.channel.chanalyzer", "ChannelAnalyzerSettings"}, // remap
    {"sdrangel.channel.chirpchatdemod", "ChirpChatDemodSettings"},
    {"sdrangel.channel.modchirpchat", "ChirpChatModSettings"},
    {"sdrangel.channel.demodatv", "ATVDemodSettings"},
    {"sdrangel.channel.demoddatv", "DATVDemodSettings"},
    {"sdrangel.channel.dsddemod", "DSDDemodSettings"},
    {"sdrangel.channel.filesink", "FileSinkSettings"},
    {"sdrangel.channeltx.filesource", "FileSourceSettings"},
    {"sdrangel.channel.freedvdemod", "FreeDVDemodSettings"},
    {"sdrangel.channeltx.freedvmod", "FreeDVModSettings"},
    {"sdrangel.channel.freqtracker", "FreqTrackerSettings"},
    {"sdrangel.channel.nfmdemod", "NFMDemodSettings"},
    {"de.maintech.sdrangelove.channel.nfm", "NFMDemodSettings"}, // remap
    {"sdrangel.channeltx.modnfm", "NFMModSettings"},
    {"sdrangel.demod.localsink", "LocalSinkSettings"},
    {"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap
    {"sdrangel.channel.localsource", "LocalSourceSettings"},
    {"sdrangel.channel.packetdemod", "PacketDemodSettings"},
    {"sdrangel.channeltx.modpacket", "PacketModSettings"},
    {"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"},
    {"sdrangel.demod.remotesink", "RemoteSinkSettings"},
    {"sdrangel.channeltx.remotesource", "RemoteSourceSettings"},
    {"sdrangel.channeltx.modssb", "SSBModSettings"},
    {"sdrangel.channel.ssbdemod", "SSBDemodSettings"},
    {"de.maintech.sdrangelove.channel.ssb", "SSBDemodSettings"}, // remap
    {"sdrangel.channeltx.udpsource", "UDPSourceSettings"},
    {"sdrangel.channeltx.udpsink", "UDPSinkSettings"}, // remap
    {"sdrangel.channel.udpsink", "UDPSinkSettings"},
    {"sdrangel.channel.udpsrc", "UDPSourceSettings"}, // remap
    {"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.airspy", "airspySettings"},
    {"sdrangel.samplesource.airspyhf", "airspyHFSettings"},
    {"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.samplesink.remoteoutput", "remoteOutputSettings"},
    {"sdrangel.samplesource.sdrplay", "sdrPlaySettings"},
    {"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"},
    {"APTDemod", "APTemodSettings"},
    {"AMDemod", "AMDemodSettings"},
    {"AMMod", "AMModSettings"},
    {"ATVDemod", "ATVDemodSettings"},
    {"ATVMod", "ATVModSettings"},
    {"BFMDemod", "BFMDemodSettings"},
    {"ChannelAnalyzer", "ChannelAnalyzerSettings"},
    {"ChirpChatDemod", "ChirpChatDemodSettings"},
    {"ChirpChatMod", "ChirpChatModSettings"},
    {"DATVDemod", "DATVDemodSettings"},
    {"DATVMod", "DATVModSettings"},
    {"DSDDemod", "DSDDemodSettings"},
    {"FileSink", "FileSinkSettings"},
    {"FileSource", "FileSourceSettings"},
    {"FreeDVDemod", "FreeDVDemodSettings"},
    {"FreeDVMod", "FreeDVModSettings"},
    {"FreqTracker", "FreqTrackerSettings"},
    {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModSettings"},
    {"NFMDemod", "NFMDemodSettings"},
    {"NFMMod", "NFMModSettings"},
    {"PacketDemod", "PacketDemodSettings"},
    {"PacketMod", "PacketModSettings"},
    {"LocalSink", "LocalSinkSettings"},
    {"LocalSource", "LocalSourceSettings"},
    {"RemoteSink", "RemoteSinkSettings"},
    {"RemoteSource", "RemoteSourceSettings"},
    {"SSBMod", "SSBModSettings"},
    {"SSBDemod", "SSBDemodSettings"},
    {"UDPSink", "UDPSinkSettings"},
    {"UDPSource", "UDPSourceSettings"},
    {"VORDemod", "VORDemodSettings"},
    {"WFMDemod", "WFMDemodSettings"},
    {"WFMMod", "WFMModSettings"},
    {"BeamSteeringCWMod", "BeamSteeringCWModSettings"},
    {"Interferometer", "InterferometerSettings"},
    {"SigMFFileSink", "SigMFFileSinkSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_channelTypeToActionsKey = {
    {"APTDemod", "APTDemodActions"},
    {"FileSink", "FileSinkActions"},
    {"FileSource", "FileSourceActions"},
    {"SigMFFileSink", "SigMFFileSinkActions"},
    {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModActions"},
    {"PacketMod", "PacketModActions"}
};

const QMap<QString, QString> WebAPIUtils::m_sourceDeviceHwIdToSettingsKey = {
    {"Airspy", "airspySettings"},
    {"AirspyHF", "airspyHFSettings"},
    {"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"},
    {"SDRplay1", "sdrPlaySettings"},
    {"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"},
    {"SigMFFileInput", "sigMFFileActions"},
    {"SoapySDR", "soapySDRInputActions"},
    {"TestSource", "testSourceActions"},
    {"USRP", "usrpSourceActions"},
    {"XTRX", "xtrxInputActions"}
};

const QMap<QString, QString> WebAPIUtils::m_sinkDeviceHwIdToSettingsKey = {
    {"AudioOutput", "AudioOutputSettings"},
    {"BladeRF1", "bladeRF1OutputSettings"},
    {"BladeRF2", "bladeRF2OutputSettings"},
    {"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 = {
    {"BladeRF2", "bladeRF2MIMOSettings"},
    {"MetisMISO", "metisMISOSettings"},
    {"TestMI", "testMISettings"},
    {"TestMOSync", "testMOSyncSettings"},
    {"XTRX", "xtrxMIMOSettings"}
};

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

const QMap<QString, QString> WebAPIUtils::m_featureTypeToSettingsKey = {
    {"APRS", "APRSSettings"},
    {"GS232Controller", "GS232ControllerSettings"},
    {"Map", "MapSettings"},
    {"PERTester", "PERTesterSettings"},
    {"SimplePTT", "SimplePTTSettings"},
    {"StarTracker", "StarTrackerSettings"},
    {"RigCtlServer", "RigCtlServerSettings"}
};

const QMap<QString, QString> WebAPIUtils::m_featureTypeToActionsKey = {
    {"Map", "MapActions"},
    {"SimplePTT", "SimplePTTActions"}
};

const QMap<QString, QString> WebAPIUtils::m_featureURIToSettingsKey = {
    {"sdrangel.feature.aprs", "APRSSettings"},
    {"sdrangel.feature.gs232controller", "GS232ControllerSettings"},
    {"sdrangel.feature.map", "MapSettings"},
    {"sdrangel.feature.pertester", "PERTesterSettings"},
    {"sdrangel.feature.simpleptt", "SimplePTTSettings"},
    {"sdrangel.feature.startracker", "StarTrackerSettings"},
    {"sdrangel.feature.rigctlserver", "RigCtlServerSettings"}
};

// 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 withing 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 withing 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;
}

// 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;
}