///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE <jon@beniston.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 "freqscanner.h"

#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>

#include <stdio.h>
#include <complex.h>

#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
#include "SWGFreqScannerSettings.h"
#include "SWGChannelReport.h"
#include "SWGChannelActions.h"

#include "device/deviceset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplesource.h"
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "settings/serializable.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "dsp/spectrumvis.h"

MESSAGE_CLASS_DEFINITION(FreqScanner::MsgConfigureFreqScanner, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportChannels, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgStartScan, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgStopScan, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgScanComplete, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgContinueScan, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgScanResult, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgStatus, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportActiveFrequency, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportActivePower, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportScanning, Message)
MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportScanRange, Message)

const char * const FreqScanner::m_channelIdURI = "sdrangel.channel.freqscanner";
const char * const FreqScanner::m_channelId = "FreqScanner";

FreqScanner::FreqScanner(DeviceAPI *deviceAPI) :
        ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
        m_deviceAPI(deviceAPI),
        m_thread(nullptr),
        m_basebandSink(nullptr),
        m_running(false),
        m_basebandSampleRate(0),
        m_availableChannelHandler({}),
        m_scanDeviceSetIndex(0),
        m_scanChannelIndex(0),
        m_state(IDLE),
        m_timeoutTimer(this)
{
    setObjectName(m_channelId);

    applySettings(m_settings, QStringList(), true);

    m_deviceAPI->addChannelSink(this);
    m_deviceAPI->addChannelSinkAPI(this);

    m_networkManager = new QNetworkAccessManager();
    QObject::connect(
        m_networkManager,
        &QNetworkAccessManager::finished,
        this,
        &FreqScanner::networkManagerFinished
    );
    QObject::connect(
        this,
        &ChannelAPI::indexInDeviceSetChanged,
        this,
        &FreqScanner::handleIndexInDeviceSetChanged
    );

    start();

    QObject::connect(&m_availableChannelHandler, &AvailableChannelOrFeatureHandler::channelsOrFeaturesChanged, this, &FreqScanner::channelsChanged);
    m_availableChannelHandler.scanAvailableChannelsAndFeatures();

    m_timeoutTimer.callOnTimeout(this, &FreqScanner::timeout);
}

FreqScanner::~FreqScanner()
{
    qDebug("FreqScanner::~FreqScanner");
    QObject::disconnect(
        m_networkManager,
        &QNetworkAccessManager::finished,
        this,
        &FreqScanner::networkManagerFinished
    );
    delete m_networkManager;
    m_deviceAPI->removeChannelSinkAPI(this);
    m_deviceAPI->removeChannelSink(this, true);

    stop();
}

void FreqScanner::setDeviceAPI(DeviceAPI *deviceAPI)
{
    if (deviceAPI != m_deviceAPI)
    {
        m_deviceAPI->removeChannelSinkAPI(this);
        m_deviceAPI->removeChannelSink(this, false);
        m_deviceAPI = deviceAPI;
        m_deviceAPI->addChannelSink(this);
        m_deviceAPI->addChannelSinkAPI(this);
    }
}

uint32_t FreqScanner::getNumberOfDeviceStreams() const
{
    return m_deviceAPI->getNbSourceStreams();
}

void FreqScanner::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
    (void) firstOfBurst;

    if (m_running) {
        m_basebandSink->feed(begin, end);
    }
}

void FreqScanner::start()
{
    QMutexLocker m_lock(&m_mutex);

    if (m_running) {
        return;
    }

    qDebug("FreqScanner::start");
    m_thread = new QThread();
    m_basebandSink = new FreqScannerBaseband(this);
    m_basebandSink->setFifoLabel(QString("%1 [%2:%3]")
        .arg(m_channelId)
        .arg(m_deviceAPI->getDeviceSetIndex())
        .arg(getIndexInDeviceSet())
    );
    m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
    m_basebandSink->setChannel(this);
    m_basebandSink->moveToThread(m_thread);

    QObject::connect(
        m_thread,
        &QThread::finished,
        m_basebandSink,
        &QObject::deleteLater
    );
    QObject::connect(
        m_thread,
        &QThread::finished,
        m_thread,
        &QThread::deleteLater
    );

    m_thread->start();

    DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
    m_basebandSink->getInputMessageQueue()->push(dspMsg);

    FreqScannerBaseband::MsgConfigureFreqScannerBaseband *msg = FreqScannerBaseband::MsgConfigureFreqScannerBaseband::create(m_settings, QStringList(), true);
    m_basebandSink->getInputMessageQueue()->push(msg);

    m_running = true;
}

void FreqScanner::stop()
{
    QMutexLocker m_lock(&m_mutex);

    if (!m_running) {
        return;
    }

    qDebug("FreqScanner::stop");
    m_running = false;
    m_thread->exit();
#ifndef __EMSCRIPTEN__
    m_thread->wait();
#endif
}

bool FreqScanner::handleMessage(const Message& cmd)
{
    if (MsgConfigureFreqScanner::match(cmd))
    {
        MsgConfigureFreqScanner& cfg = (MsgConfigureFreqScanner&) cmd;
        qDebug() << "FreqScanner::handleMessage: MsgConfigureFreqScanner";
        applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce());

        return true;
    }
    else if (DSPSignalNotification::match(cmd))
    {
        DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
        int newSampleRate = notif.getSampleRate();
        if ((newSampleRate != m_basebandSampleRate) && (m_state != IDLE))
        {
            // Restart scan if sample rate changes
            startScan();
        }
        m_basebandSampleRate = newSampleRate;
        m_centerFrequency = notif.getCenterFrequency();
        qDebug() << "FreqScanner::handleMessage: DSPSignalNotification";
        // Forward to the sink
        if (m_running)
        {
            DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
            m_basebandSink->getInputMessageQueue()->push(rep);
        }
        // Forward to GUI if any
        if (m_guiMessageQueue) {
            m_guiMessageQueue->push(new DSPSignalNotification(notif));
        }

        return true;
    }
    else if (MsgStartScan::match(cmd))
    {
        muteAll();
        startScan();

        return true;
    }
    else if (MsgStopScan::match(cmd))
    {
        stopScan();
        unmuteAll();

        return true;
    }
    else if (MsgScanResult::match(cmd))
    {
        MsgScanResult& result = (MsgScanResult&)cmd;
        const QList<MsgScanResult::ScanResult>& results = result.getScanResults();

        processScanResults(result.getFFTStartTime(), results);

        return true;
    }
    else if (MsgContinueScan::match(cmd))
    {
        continueScan();

        return true;
    }
    else
    {
        return false;
    }
}

void FreqScanner::startScan()
{
    // Start scan
    m_state = START_SCAN;
}

void FreqScanner::stopScan()
{
    // Stop scan
    m_state = IDLE;
    m_timeoutTimer.stop();

    if (m_guiMessageQueue) {
        m_guiMessageQueue->push(MsgStatus::create(""));
    }
}

void FreqScanner::setDeviceCenterFrequency(qint64 frequency)
{
    DSPDeviceSourceEngine* deviceSourceEngine = getDeviceAPI()->getDeviceSourceEngine();
    DSPDeviceMIMOEngine *deviceMIMOEngine = getDeviceAPI()->getDeviceMIMOEngine();

    if (deviceSourceEngine) // Rx device
    {
        // For RTL SDR, setCenterFrequency takes ~50ms, which means tuneTime can be 0
        getDeviceAPI()->getSampleSource()->setCenterFrequency(frequency);
    } else if (deviceMIMOEngine) { // MIMO device - I/Q stream is the same as this channel
        getDeviceAPI()->getSampleMIMO()->setSourceCenterFrequency(frequency, m_settings.m_streamIndex);
    }

    m_minFFTStartTime = QDateTime::currentDateTime().addMSecs(m_settings.m_tuneTime);
}

void FreqScanner::initScan()
{
    // if (m_scanChannelIndex < 0) { // Always false
    //     applyChannelSetting(m_settings.m_channel);
    // }
    mute(m_scanDeviceSetIndex, m_scanChannelIndex);

    if (!m_settings.m_lockDeviceFrequency && (m_centerFrequency != m_stepStartFrequency)) {
        setDeviceCenterFrequency(m_stepStartFrequency);
    }

    m_scanResults.clear();

    if (m_guiMessageQueue) {
        m_guiMessageQueue->push(FreqScanner::MsgReportScanning::create());
    }

    m_state = SCAN_FOR_MAX_POWER;
}

void FreqScanner::continueScan()
{
    m_state = SCAN_FOR_MAX_POWER;
}

void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList<MsgScanResult::ScanResult>& results)
{
    switch (m_state)
    {
    case IDLE:
        break;

    case START_SCAN:
        {
            // Create ordered list of frequencies to scan
            QList<qint64> frequencies;
            for (int i = 0; i < m_settings.m_frequencySettings.size(); i++)
            {
                if (m_settings.m_frequencySettings[i].m_enabled) {
                    frequencies.append(m_settings.m_frequencySettings[i].m_frequency);
                }
            }
            std::sort(frequencies.begin(), frequencies.end());

            if ((frequencies.size() > 0) && (m_settings.m_channelBandwidth > 0) && (m_basebandSampleRate > 0))
            {
                // Calculate how many channels can be scanned in one go
                int fftSize;
                int binsPerChannel;
                calcScannerSampleRate(m_settings.m_channelBandwidth, m_basebandSampleRate, m_scannerSampleRate, fftSize, binsPerChannel);

                // Align first frequency so we cover as many channels as possible, while skipping channel guard band (12.5% either end)
                // Can we adjust this to avoid DC bin?
                m_stepStartFrequency = frequencies.front() + m_scannerSampleRate / 2 - (m_scannerSampleRate / 8);
                m_stepStopFrequency = frequencies.back();

                // If all frequencies fit within usable bandwidth, we can have the first frequency more central
                int totalBW = frequencies.back() - frequencies.front() + 2 * m_settings.m_channelBandwidth;
                if (totalBW < m_scannerSampleRate * 0.75f)
                {
                    int spareBWEachSide = (m_scannerSampleRate - totalBW) / 2;
                    int spareChannelsEachSide = spareBWEachSide / m_settings.m_channelBandwidth;
                    int offset = spareChannelsEachSide * m_settings.m_channelBandwidth;
                    m_stepStartFrequency -= offset;
                }

                initScan();
            }
        }
        break;

    case SCAN_FOR_MAX_POWER:
        if (fftStartTime >= m_minFFTStartTime)
        {
            if (results.size() > 0) {
                m_scanResults.append(results);
            }

            // Calculate next center frequency
            const bool lockDeviceFrequency = m_settings.m_lockDeviceFrequency;
            const qint64 currentCenterFrequency = m_centerFrequency;
            bool complete = lockDeviceFrequency; // Have all frequencies been scanned?
            bool freqInRange = lockDeviceFrequency;
            qint64 nextCenterFrequency = m_centerFrequency;
            int usableBW = (m_scannerSampleRate * 3 / 4) & ~1;
            int nextFrequencyIndex = -1;
            const auto isInCurrentScanRange = [currentCenterFrequency, usableBW](qint64 frequency)
            {
                return (frequency >= currentCenterFrequency - usableBW / 2)
                    && (frequency < currentCenterFrequency + usableBW / 2);
            };

            if (!lockDeviceFrequency)
            {
                do
                {
                    if (nextCenterFrequency + usableBW / 2 > m_stepStopFrequency)
                    {
                        nextCenterFrequency = m_stepStartFrequency;
                        complete = true;
                    }
                    else
                    {
                        nextCenterFrequency += usableBW;
                        complete = false;
                    }

                    // Are any frequencies in this new range?
                    for (int i = 0; i < m_settings.m_frequencySettings.size(); i++)
                    {
                        if (m_settings.m_frequencySettings[i].m_enabled
                            && (m_settings.m_frequencySettings[i].m_frequency >= nextCenterFrequency - usableBW / 2)
                            && (m_settings.m_frequencySettings[i].m_frequency < nextCenterFrequency + usableBW / 2))
                        {
                            freqInRange = true;
                            nextFrequencyIndex = i;

                            // Do we need to realign for frequencies with wider bandwidths than default
                            if (!m_settings.m_frequencySettings[i].m_channelBandwidth.isEmpty())
                            {
                                bool ok;
                                int channelBW = m_settings.m_frequencySettings[i].m_channelBandwidth.toInt(&ok);
                                if (ok)
                                {
                                    if (channelBW >= usableBW) {
                                        nextCenterFrequency = m_settings.m_frequencySettings[i].m_frequency;
                                    } else if (m_settings.m_frequencySettings[i].m_frequency - channelBW / 2 < nextCenterFrequency - usableBW / 2) {
                                        nextCenterFrequency = m_settings.m_frequencySettings[i].m_frequency - channelBW / 2;
                                    }
                                }
                            }

                            break;
                        }
                    }
                }
                while (!complete && !freqInRange);
            }
            else
            {
                for (int i = 0; i < m_settings.m_frequencySettings.size(); i++)
                {
                    if (m_settings.m_frequencySettings[i].m_enabled
                        && isInCurrentScanRange(m_settings.m_frequencySettings[i].m_frequency))
                    {
                        nextFrequencyIndex = i;
                        break;
                    }
                }
            }

            if (complete || (m_settings.m_mode == FreqScannerSettings::MULTIPLEX))
            {
                if (m_scanResults.size() > 0)
                {
                    // Send scan results to GUI for display in table
                    if (m_guiMessageQueue)
                    {
                        FreqScanner::MsgScanResult* msg = FreqScanner::MsgScanResult::create(QDateTime());
                        QList<FreqScanner::MsgScanResult::ScanResult>& guiResults = msg->getScanResults();
                        guiResults.append(m_scanResults);
                        m_guiMessageQueue->push(msg);
                    }

                    int frequency = -1;
                    FreqScannerSettings::FrequencySettings *frequencySettings = nullptr;
                    FreqScannerSettings::FrequencySettings *activeFrequencySettings = nullptr;

                    if (m_settings.m_mode == FreqScannerSettings::MULTIPLEX)
                    {
                        if (nextFrequencyIndex >= 0)
                        {
                            activeFrequencySettings = &m_settings.m_frequencySettings[nextFrequencyIndex];
                            frequency = activeFrequencySettings->m_frequency;
                        }
                    }
                    else if (m_settings.m_priority == FreqScannerSettings::MAX_POWER)
                    {
                        Real maxPower = -200.0f;
                        Real maxVoiceActivityLevel = 0.0f;

                        // Find frequency with max power that exceeds thresholds
                        for (int i = 0; i < m_scanResults.size(); i++)
                        {
                            if (lockDeviceFrequency && !isInCurrentScanRange(m_scanResults[i].m_frequency)) {
                                continue;
                            }

                            frequencySettings = m_settings.getFrequencySettings(m_scanResults[i].m_frequency);
                            Real threshold = m_settings.getThreshold(frequencySettings);

                            if (m_settings.m_voiceSquelchType == FreqScannerSettings::VoiceSquelchType::None)
                            {
                                if ((m_scanResults[i].m_power >= threshold) 
                                && (!activeFrequencySettings || (m_scanResults[i].m_power > maxPower)))
                                {
                                    frequency = m_scanResults[i].m_frequency;
                                    maxPower = m_scanResults[i].m_power;
                                    activeFrequencySettings = frequencySettings;
                                }
                            }
                            else // VAD
                            {
                                if ((m_scanResults[i].m_voiceActivityLevel >= m_settings.m_voiceSquelchThreshold) 
                                && (!activeFrequencySettings || (m_scanResults[i].m_voiceActivityLevel > maxVoiceActivityLevel)))
                                {
                                    frequency = m_scanResults[i].m_frequency;
                                    maxVoiceActivityLevel = m_scanResults[i].m_voiceActivityLevel;
                                    activeFrequencySettings = frequencySettings;
                                }
                            }
                        }
                    }
                    else // TABLE_ORDER
                    {
                        // Find first frequency in list above threshold
                        for (int i = 0; i < m_scanResults.size(); i++)
                        {
                            int j = m_settings.m_voiceSquelchType == FreqScannerSettings::VoiceSquelchType::VoiceLsb ?
                                m_scanResults.size()-1 - i : i;

                            if (lockDeviceFrequency && !isInCurrentScanRange(m_scanResults[j].m_frequency)) {
                                continue;
                            }

                            frequencySettings = m_settings.getFrequencySettings(m_scanResults[j].m_frequency);
                            Real threshold = m_settings.getThreshold(frequencySettings);

                            if (checkThresholds(m_settings.m_voiceSquelchType, m_scanResults[j], threshold, m_settings.m_voiceSquelchThreshold))
                            {
                                frequency = m_scanResults[j].m_frequency;
                                activeFrequencySettings = frequencySettings;
                                break;
                            }
                        }
                    }

                    if (m_settings.m_mode != FreqScannerSettings::SCAN_ONLY)
                    {
                        // Were any frequencies found to be active?
                        //if (maxPower >= m_settings.m_threshold)
                        if (activeFrequencySettings)
                        {
                            // Tune device/channel to frequency
                            int offset;
                            if ((frequency < m_centerFrequency - usableBW / 2) || (frequency >= m_centerFrequency + usableBW / 2))
                            {
                                nextCenterFrequency = frequency;
                                offset = 0;
                            }
                            else
                            {
                                nextCenterFrequency = m_centerFrequency;
                                offset = frequency - m_centerFrequency;
                            }

                            // Ensure we have minimum offset from DC
                            if (!lockDeviceFrequency)
                            {
                                if (offset >= 0)
                                {
                                    while (offset < m_settings.m_channelFrequencyOffset)
                                    {
                                        nextCenterFrequency -= m_settings.m_channelBandwidth;
                                        offset += m_settings.m_channelBandwidth;
                                    }
                                }
                                else
                                {
                                    while (abs(offset) < m_settings.m_channelFrequencyOffset)
                                    {
                                        nextCenterFrequency += m_settings.m_channelBandwidth;
                                        offset -= m_settings.m_channelBandwidth;
                                    }
                                }
                            }

                            //qDebug() << "Tuning to active freq:" << frequency << "m_centerFrequency" << m_centerFrequency << "nextCenterFrequency" << nextCenterFrequency << "offset: " << offset << "deviceset: R" << m_scanDeviceSetIndex << ":" << m_scanChannelIndex;

                            QString channel = m_settings.m_channel;
                            if (!activeFrequencySettings->m_channel.isEmpty()) {
                                channel = activeFrequencySettings->m_channel;
                            }
                            applyChannelSetting(channel);

                            // Tune the channel
                            ChannelWebAPIUtils::setFrequencyOffset(m_scanDeviceSetIndex, m_scanChannelIndex, offset + m_settings.m_channelShift);

                            // Unmute the channel
                            unmute(m_scanDeviceSetIndex, m_scanChannelIndex);

                            // Apply squelch
                            if (!activeFrequencySettings->m_squelch.isEmpty())
                            {
                                bool ok;
                                Real squelch = activeFrequencySettings->m_squelch.toFloat(&ok);
                                if (ok) {
                                    ChannelWebAPIUtils::patchChannelSetting(m_scanDeviceSetIndex, m_scanChannelIndex, "squelch", squelch);
                                }
                            }

                            m_activeFrequency = frequency;

                            if (m_settings.m_mode == FreqScannerSettings::SINGLE)
                            {
                                // Scan complete
                                if (m_guiMessageQueue) {
                                    m_guiMessageQueue->push(MsgScanComplete::create());
                                }
                                m_state = IDLE;
                            }
                            else if (m_settings.m_mode == FreqScannerSettings::MULTIPLEX)
                            {
                                // Wait for user-specified receive time before moving to next frequency
                                m_timeoutTimer.setSingleShot(true);
                                m_timeoutTimer.start((int)(m_settings.m_retransmitTime * 1000.0));
                                m_state = WAIT_FOR_RX_TIME;
                            }
                            else
                            {
                                // Wait for transmission to finish
                                m_state = WAIT_FOR_END_TX;
                            }

                            // Becareful to only do this at the end here, as it can recursively call handleMessage with new settings
                            if (m_guiMessageQueue) {
                                m_guiMessageQueue->push(MsgReportActiveFrequency::create(m_activeFrequency));
                            }
                        }
                        else
                        {
                            if (m_guiMessageQueue) {
                                m_guiMessageQueue->push(MsgStatus::create("Scanning..."));
                            }
                        }
                    }
                }
            }

            if (m_settings.m_lockDeviceFrequency) {
                nextCenterFrequency = m_centerFrequency;
            }

            if (nextCenterFrequency != m_centerFrequency)
            {
                setDeviceCenterFrequency(nextCenterFrequency);
            }

            if (complete)
            {
                m_scanResultsForReport = m_scanResults;
                m_scanResults.clear();
            }
        }
        break;

    case WAIT_FOR_END_TX:
        for (int i = 0; i < results.size(); i++)
        {
            if (results[i].m_frequency == m_activeFrequency)
            {
                if (m_guiMessageQueue) {
                    m_guiMessageQueue->push(MsgReportActivePower::create(results[i].m_power));
                }

                // Wait until power drops below threshold
                FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(m_activeFrequency);
                Real threshold = m_settings.getThreshold(frequencySettings);
                if (results[i].m_power < threshold) 
                {
                    m_timeoutTimer.setSingleShot(true);
                    m_timeoutTimer.start((int)(m_settings.m_retransmitTime * 1000.0));
                    m_state = WAIT_FOR_RETRANSMISSION;
                    break;
                }
            }
        }
        break;

    case WAIT_FOR_RETRANSMISSION:
        for (int i = 0; i < results.size(); i++)
        {
            if (results[i].m_frequency == m_activeFrequency)
            {
                if (m_guiMessageQueue) {
                    m_guiMessageQueue->push(MsgReportActivePower::create(results[i].m_power));
                }

                // Check if power has returned to being above threshold
                FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(m_activeFrequency);
                Real threshold = m_settings.getThreshold(frequencySettings);
                
                if (checkThresholds(m_settings.m_voiceSquelchType, results[i], threshold, m_settings.m_voiceSquelchThreshold))
                {
                    if (m_settings.m_voiceSquelchType != FreqScannerSettings::VoiceSquelchType::None) {
                        qDebug("FreqScanner::processScanResults: WAIT_FOR_RETRANSMISSION restart: frequency: %lld, voice score: %f", m_activeFrequency, results[i].m_voiceActivityLevel);
                    }
                    m_timeoutTimer.stop();
                    m_state = WAIT_FOR_END_TX;
                }
            }
        }
        break;

    case WAIT_FOR_RX_TIME:
        for (int i = 0; i < results.size(); i++)
        {
            if (results[i].m_frequency == m_activeFrequency)
            {
                if (m_guiMessageQueue) {
                    m_guiMessageQueue->push(MsgReportActivePower::create(results[i].m_power));
                }
            }
        }
        break;

    }
}

void FreqScanner::timeout()
{
    if (m_settings.m_mode == FreqScannerSettings::MULTIPLEX)
    {
        // Move to next frequency
        m_state = SCAN_FOR_MAX_POWER;
    }
    else
    {
        // Power hasn't returned above threshold - Restart scan
        initScan();
    }
}

void FreqScanner::calcScannerSampleRate(int channelBW, int basebandSampleRate, int& scannerSampleRate, int& fftSize, int& binsPerChannel)
{
    const int maxFFTSize = 16384;
    int minBinsPerChannel = 8;

    if (m_settings.m_voiceSquelchType == FreqScannerSettings::VoiceSquelchType::VoiceLsb
        || m_settings.m_voiceSquelchType == FreqScannerSettings::VoiceSquelchType::VoiceUsb)
    {
        minBinsPerChannel = channelBW / 20; // we want at most 20 Hz per bin
    }

    // Base FFT size on that used for main spectrum
    std::vector<DeviceSet*>& deviceSets = MainCore::instance()->getDeviceSets();
    DeviceSet* deviceSet = deviceSets[m_deviceAPI->getDeviceSetIndex()];
    const SpectrumSettings& spectrumSettings = deviceSet->m_spectrumVis->getSettings();
    fftSize = spectrumSettings.m_fftSize;

    // But ensure we have several bins per channel
    // Adjust sample rate, to ensure we don't get massive FFT size
    scannerSampleRate = basebandSampleRate;
    if (scannerSampleRate < channelBW) {
        channelBW = scannerSampleRate; // Prevent divide by 0
    }
    while (fftSize / (scannerSampleRate / channelBW) < minBinsPerChannel)
    {
        if (fftSize == maxFFTSize) {
            scannerSampleRate /= 2;
        } else {
            fftSize *= 2;
        }
    }

    binsPerChannel = fftSize / (scannerSampleRate / (float)channelBW);
}

void FreqScanner::setCenterFrequency(qint64 frequency)
{
    FreqScannerSettings settings = m_settings;
    settings.m_inputFrequencyOffset = frequency;
    applySettings(settings, {"inputFrequencyOffset"}, false);

    if (m_guiMessageQueue) // forward to GUI if any
    {
        MsgConfigureFreqScanner *msgToGUI = MsgConfigureFreqScanner::create(settings, {"inputFrequencyOffset"}, false);
        m_guiMessageQueue->push(msgToGUI);
    }
}

// Ensure a channel is muted and remember if we muted it, as opposed to user having manually muted it
void FreqScanner::mute(unsigned int deviceSetIndex, unsigned int channelIndex)
{
    QString channel = QString("R%1:%2").arg(deviceSetIndex).arg(channelIndex);

    if (m_autoMutedChannels.contains(channel) && (m_autoMutedChannels[channel] == true)) {
        return; // Already muted
    }

    bool muted = false;
    if (ChannelWebAPIUtils::getAudioMute(deviceSetIndex, channelIndex, muted))
    {
        if (!muted)
        {
            ChannelWebAPIUtils::setAudioMute(deviceSetIndex, channelIndex, true);
            m_autoMutedChannels.insert(channel, true);
        }
        else
        {
            m_autoMutedChannels.insert(channel, false);
        }
    }
}

// Unmute channel if it was auto-muted
void FreqScanner::unmute(unsigned int deviceSetIndex, unsigned int channelIndex)
{
    QString channel = QString("R%1:%2").arg(deviceSetIndex).arg(channelIndex);
    bool autoMuted = m_autoMutedChannels[channel];

    if (autoMuted)
    {
        ChannelWebAPIUtils::setAudioMute(deviceSetIndex, channelIndex, false);
        m_autoMutedChannels.insert(channel, false);
    }
}

// Mute all channels
void FreqScanner::muteAll()
{
    QStringList channels;

    channels.append(m_settings.m_channel);
    for (int i = 0; i < m_settings.m_frequencySettings.size(); i++)
    {
        QString channel = m_settings.m_frequencySettings[i].m_channel;
        if (!channel.isEmpty() && !channels.contains(channel)) {
            channels.append(channel);
        }
    }

    m_autoMutedChannels.clear();
    for (const auto& channel : channels)
    {
        unsigned int deviceSetIndex, channelIndex;

        if (MainCore::getDeviceAndChannelIndexFromId(channel, deviceSetIndex, channelIndex)) {
            mute(deviceSetIndex, channelIndex);
        }
    }
}

// Unmute all channels that were auto-muted
void FreqScanner::unmuteAll()
{
    for (auto it = m_autoMutedChannels.begin(); it != m_autoMutedChannels.end(); ++it)
    {
        QString channel = it.key();
        bool autoMuted = it.value();
        if (autoMuted)
        {
            unsigned int deviceSetIndex, channelIndex;
            if (MainCore::getDeviceAndChannelIndexFromId(channel, deviceSetIndex, channelIndex)) {
                unmute(deviceSetIndex, channelIndex);
            }
        }
    }
    m_autoMutedChannels.clear();
}

bool FreqScanner::checkThresholds(
    FreqScannerSettings::VoiceSquelchType voiceSquelchType, 
    const FreqScanner::MsgScanResult::ScanResult& result,
    Real powerThreshold,
    Real voiceSquelchThreshold
)
{
    return (((result.m_power >= powerThreshold) && (voiceSquelchType == FreqScannerSettings::VoiceSquelchType::None)) 
    || (result.m_voiceActivityLevel >= voiceSquelchThreshold));
}

void FreqScanner::applyChannelSetting(const QString& channel)
{
    if (!MainCore::getDeviceAndChannelIndexFromId(channel, m_scanDeviceSetIndex, m_scanChannelIndex)) {
        qDebug() << "FreqScanner::applySettings: Failed to parse channel" << channel;
    }
}

void FreqScanner::applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force)
{
    qDebug() << "FreqScanner::applySettings:"
            << settings.getDebugString(settingsKeys, force)
            << " force: " << force;

    if (settingsKeys.contains("streamIndex"))
    {
        if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
        {
            m_deviceAPI->removeChannelSinkAPI(this);
            m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
            m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
            m_deviceAPI->addChannelSinkAPI(this);
            //FIXME:scanAvailableChannels(); // re-scan
            emit streamIndexChanged(settings.m_streamIndex);
        }
    }

    if (m_running)
    {
        FreqScannerBaseband::MsgConfigureFreqScannerBaseband *msg = FreqScannerBaseband::MsgConfigureFreqScannerBaseband::create(settings, settingsKeys, force);
        m_basebandSink->getInputMessageQueue()->push(msg);
    }

    if (settings.m_useReverseAPI)
    {
        bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) ||
            settingsKeys.contains("reverseAPIAddress") ||
            settingsKeys.contains("reverseAPIPort") ||
            settingsKeys.contains("reverseAPIDeviceIndex") ||
            settingsKeys.contains("reverseAPIChannelIndex");
        webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
    }

    if (settingsKeys.contains("frequencySettings")
        || settingsKeys.contains("priority")
        || settingsKeys.contains("measurement")
        || settingsKeys.contains("mode")
        || settingsKeys.contains("channelBandwidth")
        || force)
    {
        // Restart scan if any settings change
        if (m_state != IDLE) {
            startScan();
        }
    }

    if (force) {
        m_settings = settings;
    } else {
        m_settings.applySettings(settingsKeys, settings);
    }
}

QByteArray FreqScanner::serialize() const
{
    return m_settings.serialize();
}

bool FreqScanner::deserialize(const QByteArray& data)
{
    if (m_settings.deserialize(data))
    {
        MsgConfigureFreqScanner *msg = MsgConfigureFreqScanner::create(m_settings, QStringList(), true);
        m_inputMessageQueue.push(msg);
        return true;
    }
    else
    {
        m_settings.resetToDefaults();
        MsgConfigureFreqScanner *msg = MsgConfigureFreqScanner::create(m_settings, QStringList(), true);
        m_inputMessageQueue.push(msg);
        return false;
    }
}

int FreqScanner::webapiSettingsGet(
        SWGSDRangel::SWGChannelSettings& response,
        QString& errorMessage)
{
    (void) errorMessage;
    response.setFreqScannerSettings(new SWGSDRangel::SWGFreqScannerSettings());
    response.getFreqScannerSettings()->init();
    webapiFormatChannelSettings(response, m_settings);
    return 200;
}

int FreqScanner::webapiWorkspaceGet(
        SWGSDRangel::SWGWorkspaceInfo& response,
        QString& errorMessage)
{
    (void) errorMessage;
    response.setIndex(m_settings.m_workspaceIndex);
    return 200;
}

int FreqScanner::webapiSettingsPutPatch(
        bool force,
        const QStringList& channelSettingsKeys,
        SWGSDRangel::SWGChannelSettings& response,
        QString& errorMessage)
{
    (void) errorMessage;
    FreqScannerSettings settings = m_settings;
    webapiUpdateChannelSettings(settings, channelSettingsKeys, response);

    MsgConfigureFreqScanner *msg = MsgConfigureFreqScanner::create(settings, channelSettingsKeys, force);
    m_inputMessageQueue.push(msg);

    qDebug("FreqScanner::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
    if (m_guiMessageQueue) // forward to GUI if any
    {
        MsgConfigureFreqScanner *msgToGUI = MsgConfigureFreqScanner::create(settings, channelSettingsKeys, force);
        m_guiMessageQueue->push(msgToGUI);
    }

    webapiFormatChannelSettings(response, settings);

    return 200;
}

int FreqScanner::webapiReportGet(
            SWGSDRangel::SWGChannelReport& response,
            QString& errorMessage)
{
    (void) errorMessage;
    response.setFreqScannerReport(new SWGSDRangel::SWGFreqScannerReport());
    response.getFreqScannerReport()->init();
    webapiFormatChannelReport(response);
    return 200;
}

int FreqScanner::webapiActionsPost(
        const QStringList& channelActionsKeys,
        SWGSDRangel::SWGChannelActions& query,
        QString& errorMessage)
{
    SWGSDRangel::SWGFreqScannerActions *swgFreqScannerActions = query.getFreqScannerActions();

    if (swgFreqScannerActions)
    {
        if (channelActionsKeys.contains("run"))
        {
            bool run = swgFreqScannerActions->getRun() != 0;
            if (run)
            {
                MsgStartScan *start = MsgStartScan::create();
                if (getMessageQueueToGUI()) {
                    getMessageQueueToGUI()->push(start);
                } else {
                    getInputMessageQueue()->push(start);
                }
            }
            else
            {
                MsgStopScan *stop = MsgStopScan::create();
                if (getMessageQueueToGUI()) {
                    getMessageQueueToGUI()->push(stop);
                } else {
                    getInputMessageQueue()->push(stop);
                }
            }
        }

        return 202;
    }
    else
    {
        errorMessage = "Missing FreqScannerActions in query";
        return 400;
    }
}

void FreqScanner::webapiUpdateChannelSettings(
        FreqScannerSettings& settings,
        const QStringList& channelSettingsKeys,
        SWGSDRangel::SWGChannelSettings& response)
{
    if (channelSettingsKeys.contains("channelFrequencyOffset")) {
        settings.m_channelFrequencyOffset = response.getFreqScannerSettings()->getChannelFrequencyOffset();
    }
    if (channelSettingsKeys.contains("channelBandwidth")) {
        settings.m_channelBandwidth = response.getFreqScannerSettings()->getChannelBandwidth();
    }
    if (channelSettingsKeys.contains("channelShift")) {
        settings.m_channelShift = response.getFreqScannerSettings()->getChannelShift();
    }
    if (channelSettingsKeys.contains("threshold")) {
        settings.m_threshold = response.getFreqScannerSettings()->getThreshold();
    }
    if (channelSettingsKeys.contains("frequencies"))
    {
        settings.m_frequencySettings.clear();
        QList<SWGSDRangel::SWGFreqScannerFrequency *> *frequencies = response.getFreqScannerSettings()->getFrequencies();
        if (frequencies)
        {
            for (const auto frequency : *frequencies)
            {
                FreqScannerSettings::FrequencySettings freqSetting;
                freqSetting.m_frequency = frequency->getFrequency();
                if (frequency->getNotes()) {
                    freqSetting.m_notes = *frequency->getNotes();
                }
                if (frequency->getChannel()) {
                    freqSetting.m_channel = *frequency->getChannel();
                }
                if (frequency->getChannelBandwidth()) {
                    freqSetting.m_channelBandwidth = *frequency->getChannelBandwidth();
                }
                if (frequency->getThreshold()) {
                    freqSetting.m_threshold = *frequency->getThreshold();
                }
                if (frequency->getSquelch()) {
                    freqSetting.m_squelch = *frequency->getSquelch();
                }
                settings.m_frequencySettings.append(freqSetting);
            }
        }
    }
    if (channelSettingsKeys.contains("rgbColor")) {
        settings.m_rgbColor = response.getFreqScannerSettings()->getRgbColor();
    }
    if (channelSettingsKeys.contains("title")) {
        settings.m_title = *response.getFreqScannerSettings()->getTitle();
    }
    if (channelSettingsKeys.contains("streamIndex")) {
        settings.m_streamIndex = response.getFreqScannerSettings()->getStreamIndex();
    }
    if (channelSettingsKeys.contains("useReverseAPI")) {
        settings.m_useReverseAPI = response.getFreqScannerSettings()->getUseReverseApi() != 0;
    }
    if (channelSettingsKeys.contains("reverseAPIAddress")) {
        settings.m_reverseAPIAddress = *response.getFreqScannerSettings()->getReverseApiAddress();
    }
    if (channelSettingsKeys.contains("reverseAPIPort")) {
        settings.m_reverseAPIPort = response.getFreqScannerSettings()->getReverseApiPort();
    }
    if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
        settings.m_reverseAPIDeviceIndex = response.getFreqScannerSettings()->getReverseApiDeviceIndex();
    }
    if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
        settings.m_reverseAPIChannelIndex = response.getFreqScannerSettings()->getReverseApiChannelIndex();
    }
    if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
        settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getFreqScannerSettings()->getChannelMarker());
    }
    if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
        settings.m_rollupState->updateFrom(channelSettingsKeys, response.getFreqScannerSettings()->getRollupState());
    }
}

QList<SWGSDRangel::SWGFreqScannerFrequency *> *FreqScanner::createFrequencyList(const FreqScannerSettings& settings)
{
    QList<SWGSDRangel::SWGFreqScannerFrequency *> *frequencies = new QList<SWGSDRangel::SWGFreqScannerFrequency *>();
    for (int i = 0; i < settings.m_frequencySettings.size(); i++)
    {
        SWGSDRangel::SWGFreqScannerFrequency *frequency = new SWGSDRangel::SWGFreqScannerFrequency();
        frequency->init();
        frequency->setFrequency(settings.m_frequencySettings[i].m_frequency);
        frequency->setEnabled(settings.m_frequencySettings[i].m_enabled);
        if (!settings.m_frequencySettings[i].m_notes.isEmpty()) {
            frequency->setNotes(new QString(settings.m_frequencySettings[i].m_notes));
        }
        if (!settings.m_frequencySettings[i].m_channel.isEmpty()) {
            frequency->setChannel(new QString(settings.m_frequencySettings[i].m_channel));
        }
        if (!settings.m_frequencySettings[i].m_channelBandwidth.isEmpty()) {
            frequency->setChannelBandwidth(new QString(settings.m_frequencySettings[i].m_channelBandwidth));
        }
        if (!settings.m_frequencySettings[i].m_threshold.isEmpty()) {
            frequency->setThreshold(new QString(settings.m_frequencySettings[i].m_threshold));
        }
        if (!settings.m_frequencySettings[i].m_squelch.isEmpty()) {
            frequency->setSquelch(new QString(settings.m_frequencySettings[i].m_squelch));
        }
        frequencies->append(frequency);
    }
    return frequencies;
}

void FreqScanner::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreqScannerSettings& settings)
{
    response.getFreqScannerSettings()->setChannelFrequencyOffset(settings.m_channelFrequencyOffset);
    response.getFreqScannerSettings()->setChannelBandwidth(settings.m_channelBandwidth);
    response.getFreqScannerSettings()->setChannelShift(settings.m_channelShift);
    response.getFreqScannerSettings()->setThreshold(settings.m_threshold);

    QList<SWGSDRangel::SWGFreqScannerFrequency *> *frequencies = createFrequencyList(settings);
    if (response.getFreqScannerSettings()->getFrequencies()) {
        *response.getFreqScannerSettings()->getFrequencies() = *frequencies;
    } else {
        response.getFreqScannerSettings()->setFrequencies(frequencies);
    }

    response.getFreqScannerSettings()->setRgbColor(settings.m_rgbColor);
    if (response.getFreqScannerSettings()->getTitle()) {
        *response.getFreqScannerSettings()->getTitle() = settings.m_title;
    } else {
        response.getFreqScannerSettings()->setTitle(new QString(settings.m_title));
    }

    response.getFreqScannerSettings()->setStreamIndex(settings.m_streamIndex);
    response.getFreqScannerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);

    if (response.getFreqScannerSettings()->getReverseApiAddress()) {
        *response.getFreqScannerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
    } else {
        response.getFreqScannerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
    }

    response.getFreqScannerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
    response.getFreqScannerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
    response.getFreqScannerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);

    if (settings.m_channelMarker)
    {
        if (response.getFreqScannerSettings()->getChannelMarker())
        {
            settings.m_channelMarker->formatTo(response.getFreqScannerSettings()->getChannelMarker());
        }
        else
        {
            SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
            settings.m_channelMarker->formatTo(swgChannelMarker);
            response.getFreqScannerSettings()->setChannelMarker(swgChannelMarker);
        }
    }

    if (settings.m_rollupState)
    {
        if (response.getFreqScannerSettings()->getRollupState())
        {
            settings.m_rollupState->formatTo(response.getFreqScannerSettings()->getRollupState());
        }
        else
        {
            SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
            settings.m_rollupState->formatTo(swgRollupState);
            response.getFreqScannerSettings()->setRollupState(swgRollupState);
        }
    }
}

void FreqScanner::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
    response.getFreqScannerReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
    response.getFreqScannerReport()->setScanState((int) m_state);

    QList<SWGSDRangel::SWGFreqScannerChannelState *> *list = response.getFreqScannerReport()->getChannelState();

    for (int i = 0; i < m_scanResultsForReport.size(); i++)
    {
        SWGSDRangel::SWGFreqScannerChannelState *channelState = new SWGSDRangel::SWGFreqScannerChannelState();
        channelState->setFrequency(m_scanResultsForReport[i].m_frequency);
        channelState->setPower(m_scanResultsForReport[i].m_power);
        list->append(channelState);
    }
}

void FreqScanner::webapiReverseSendSettings(const QStringList& channelSettingsKeys, const FreqScannerSettings& settings, bool force)
{
    SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
    webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);

    QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
            .arg(settings.m_reverseAPIAddress)
            .arg(settings.m_reverseAPIPort)
            .arg(settings.m_reverseAPIDeviceIndex)
            .arg(settings.m_reverseAPIChannelIndex);
    m_networkRequest.setUrl(QUrl(channelSettingsURL));
    m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    QBuffer *buffer = new QBuffer();
    buffer->open((QBuffer::ReadWrite));
    buffer->write(swgChannelSettings->asJson().toUtf8());
    buffer->seek(0);

    // Always use PATCH to avoid passing reverse API settings
    QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
    buffer->setParent(reply);

    delete swgChannelSettings;
}

void FreqScanner::webapiFormatChannelSettings(
        const QStringList& channelSettingsKeys,
        SWGSDRangel::SWGChannelSettings *swgChannelSettings,
        const FreqScannerSettings& settings,
        bool force
)
{
    swgChannelSettings->setDirection(0); // Single sink (Rx)
    swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
    swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
    swgChannelSettings->setChannelType(new QString("FreqScanner"));
    swgChannelSettings->setFreqScannerSettings(new SWGSDRangel::SWGFreqScannerSettings());
    SWGSDRangel::SWGFreqScannerSettings *swgFreqScannerSettings = swgChannelSettings->getFreqScannerSettings();

    // transfer data that has been modified. When force is on transfer all data except reverse API data

    if (channelSettingsKeys.contains("channelFrequencyOffset") || force) {
        swgFreqScannerSettings->setChannelFrequencyOffset(settings.m_channelFrequencyOffset);
    }
    if (channelSettingsKeys.contains("channelBandwidth") || force) {
        swgFreqScannerSettings->setChannelBandwidth(settings.m_channelBandwidth);
    }
    if (channelSettingsKeys.contains("channelShift") || force) {
        swgFreqScannerSettings->setChannelShift(settings.m_channelShift);
    }
    if (channelSettingsKeys.contains("threshold") || force) {
        swgFreqScannerSettings->setThreshold(settings.m_threshold);
    }
    if (channelSettingsKeys.contains("frequencies") || force) {
        QList<SWGSDRangel::SWGFreqScannerFrequency *> *frequencies = createFrequencyList(settings);
        if (swgFreqScannerSettings->getFrequencies()) {
            *swgFreqScannerSettings->getFrequencies() = *frequencies;
        } else {
            swgFreqScannerSettings->setFrequencies(frequencies);
        }
    }

    if (channelSettingsKeys.contains("rgbColor") || force) {
        swgFreqScannerSettings->setRgbColor(settings.m_rgbColor);
    }
    if (channelSettingsKeys.contains("title") || force) {
        swgFreqScannerSettings->setTitle(new QString(settings.m_title));
    }
    if (channelSettingsKeys.contains("streamIndex") || force) {
        swgFreqScannerSettings->setStreamIndex(settings.m_streamIndex);
    }

    if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
    {
        SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
        settings.m_channelMarker->formatTo(swgChannelMarker);
        swgFreqScannerSettings->setChannelMarker(swgChannelMarker);
    }

    if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force))
    {
        SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
        settings.m_rollupState->formatTo(swgRollupState);
        swgFreqScannerSettings->setRollupState(swgRollupState);
    }
}

void FreqScanner::networkManagerFinished(QNetworkReply *reply)
{
    QNetworkReply::NetworkError replyError = reply->error();

    if (replyError)
    {
        qWarning() << "FreqScanner::networkManagerFinished:"
                << " error(" << (int) replyError
                << "): " << replyError
                << ": " << reply->errorString();
    }
    else
    {
        QString answer = reply->readAll();
        answer.chop(1); // remove last \n
        qDebug("FreqScanner::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
    }

    reply->deleteLater();
}

void FreqScanner::handleIndexInDeviceSetChanged(int index)
{
    if (!m_running || (index < 0)) {
        return;
    }

    QString fifoLabel = QString("%1 [%2:%3]")
        .arg(m_channelId)
        .arg(m_deviceAPI->getDeviceSetIndex())
        .arg(index);
    m_basebandSink->setFifoLabel(fifoLabel);
}

void FreqScanner::channelsChanged(const QStringList& renameFrom, const QStringList& renameTo)
{
    m_availableChannels = m_availableChannelHandler.getAvailableChannelOrFeatureList();
    notifyUpdateChannels(renameFrom, renameTo);
}

void FreqScanner::notifyUpdateChannels(const QStringList& renameFrom, const QStringList& renameTo)
{
    if (getMessageQueueToGUI())
    {
        MsgReportChannels* msgToGUI = MsgReportChannels::create(renameFrom, renameTo);
        msgToGUI->getChannels() = m_availableChannels;
        getMessageQueueToGUI()->push(msgToGUI);
    }
}
