///////////////////////////////////////////////////////////////////////////////////
// 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 .          //
///////////////////////////////////////////////////////////////////////////////////
#include 
#include "SWGDeviceState.h"
#include "SWGDeviceSettings.h"
#include "SWGChannelSettings.h"
#include "SWGSuccessResponse.h"
#include "SWGErrorResponse.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
#include "dsp/devicesamplesource.h"
#include "dsp/devicesamplesink.h"
#include "channel/channelapi.h"
#include "channel/channelwebapiutils.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
#include "maincore.h"
#include "vorlocalizerreport.h"
#include "vorlocalizerworker.h"
MESSAGE_CLASS_DEFINITION(VorLocalizerWorker::MsgConfigureVORLocalizerWorker, Message)
MESSAGE_CLASS_DEFINITION(VorLocalizerWorker::MsgRefreshChannels, Message)
class DSPDeviceSourceEngine;
VorLocalizerWorker::VorLocalizerWorker(WebAPIAdapterInterface *webAPIAdapterInterface) :
    m_webAPIAdapterInterface(webAPIAdapterInterface),
    m_msgQueueToFeature(nullptr),
    m_availableChannels(nullptr),
    m_updateTimer(this),
    m_rrTimer(this)
{
    qDebug("VorLocalizerWorker::VorLocalizerWorker");
	connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
}
VorLocalizerWorker::~VorLocalizerWorker()
{
    m_inputMessageQueue.clear();
}
void VorLocalizerWorker::reset()
{
    QMutexLocker mutexLocker(&m_mutex);
    m_inputMessageQueue.clear();
}
void VorLocalizerWorker::startWork()
{
    QMutexLocker mutexLocker(&m_mutex);
    connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
    connect(&m_rrTimer, SIGNAL(timeout()), this, SLOT(rrNextTurn()));
    connect(thread(), SIGNAL(started()), this, SLOT(started()));
    connect(thread(), SIGNAL(finished()), this, SLOT(finished()));
}
// startWork() is called from main thread. Timers/sockets need to be started on worker thread
void VorLocalizerWorker::started()
{
    m_rrTimer.start(m_settings.m_rrTime * 1000);
    disconnect(thread(), SIGNAL(started()), this, SLOT(started()));
}
void VorLocalizerWorker::stopWork()
{
    QMutexLocker mutexLocker(&m_mutex);
    disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
void VorLocalizerWorker::finished()
{
    m_rrTimer.stop();
    disconnect(&m_rrTimer, SIGNAL(timeout()), this, SLOT(rrNextTurn()));
    disconnect(thread(), SIGNAL(finished()), this, SLOT(finished()));
}
void VorLocalizerWorker::handleInputMessages()
{
	Message* message;
	while ((message = m_inputMessageQueue.pop()) != nullptr)
	{
		if (handleMessage(*message)) {
			delete message;
		}
	}
}
bool VorLocalizerWorker::handleMessage(const Message& cmd)
{
    if (MsgConfigureVORLocalizerWorker::match(cmd))
    {
        QMutexLocker mutexLocker(&m_mutex);
        MsgConfigureVORLocalizerWorker& cfg = (MsgConfigureVORLocalizerWorker&) cmd;
        qDebug() << "VorLocalizerWorker::handleMessage: MsgConfigureVORLocalizerWorker";
        applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce());
        return true;
    }
    else if (MsgRefreshChannels::match(cmd))
    {
        qDebug() << "VorLocalizerWorker::handleMessage: MsgRefreshChannels";
        updateChannels();
        return true;
    }
    else
    {
        return false;
    }
}
void VorLocalizerWorker::applySettings(const VORLocalizerSettings& settings, const QList& settingsKeys, bool force)
{
    qDebug() << "VorLocalizerWorker::applySettings:" << settings.getDebugString(settingsKeys, force) << " force: " << force;
    // Remove sub-channels no longer needed
    for (int i = 0; i < m_vorChannels.size(); i++)
    {
        if (!settings.m_subChannelSettings.contains(m_vorChannels[i].m_subChannelId))
        {
            qDebug() << "VorLocalizerWorker::applySettings: Removing sink " << m_vorChannels[i].m_subChannelId;
            removeVORChannel(m_vorChannels[i].m_subChannelId);
        }
    }
    // Add new sub channels
    QHash::const_iterator itr = settings.m_subChannelSettings.begin();
    while (itr != settings.m_subChannelSettings.end())
    {
        const VORLocalizerSubChannelSettings& subChannelSettings = itr.value();
        qDebug() << "VorLocalizerWorker::applySettings: subchannel " << subChannelSettings.m_id;
        int j = 0;
        for (; j < m_vorChannels.size(); j++)
        {
            if (subChannelSettings.m_id == m_vorChannels[j].m_subChannelId)
            {
                qDebug() << "VorLocalizerWorker::applySettings: subchannel "
                    << subChannelSettings.m_id
                    << "already present";
                break;
            }
        }
        if (j == m_vorChannels.size())
        {
            // Add a sub-channel sink
            qDebug() << "VorLocalizerWorker::applySettings: Adding subchannel " << subChannelSettings.m_id;
            addVORChannel(subChannelSettings);
        }
        ++itr;
    }
    for (auto subChannelSetting : settings.m_subChannelSettings)
    {
        int navId = subChannelSetting.m_id;
        if (m_settings.m_subChannelSettings.contains(navId))
        {
            if (subChannelSetting.m_audioMute != m_settings.m_subChannelSettings[navId].m_audioMute)
            {
                qDebug() << "VorLocalizerWorker::applySettings: audioMute:" << subChannelSetting.m_audioMute;
                setAudioMute(navId, subChannelSetting.m_audioMute);
            }
        }
    }
    if (settingsKeys.contains("rrTime") || force) {
        m_rrTimer.start(settings.m_rrTime * 1000);
    }
    if (force) {
        m_settings = settings;
    } else {
        m_settings.applySettings(settingsKeys, settings);
    }
}
void VorLocalizerWorker::updateHardware()
{
    SWGSDRangel::SWGSuccessResponse response;
    SWGSDRangel::SWGErrorResponse error;
    m_updateTimer.stop();
    m_mutex.unlock();
}
quint64 VorLocalizerWorker::getDeviceCenterFrequency(int deviceIndex)
{
    std::vector deviceSets = MainCore::instance()->getDeviceSets();
    if (deviceIndex < (int) deviceSets.size())
    {
        DeviceSet *deviceSet = deviceSets[deviceIndex];
        if (deviceSet->m_deviceSourceEngine)
        {
            DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
            return source->getCenterFrequency();
        }
        else if (deviceSet->m_deviceSinkEngine)
        {
            DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
            return sink->getCenterFrequency();
        }
    }
    return 0;
}
int VorLocalizerWorker::getDeviceSampleRate(int deviceIndex)
{
    std::vector deviceSets = MainCore::instance()->getDeviceSets();
    if (deviceIndex < (int) deviceSets.size())
    {
        DeviceSet *deviceSet = deviceSets[deviceIndex];
        if (deviceSet->m_deviceSourceEngine)
        {
            DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
            return source->getSampleRate();
        }
        else if (deviceSet->m_deviceSinkEngine)
        {
            DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
            return sink->getSampleRate();
        }
    }
    return 0;
}
// Does this device have a center frequency setting (FileInput doesn't)
bool VorLocalizerWorker::hasCenterFrequencySetting(int deviceIndex)
{
    double deviceFrequency;
    return !ChannelWebAPIUtils::getCenterFrequency(deviceIndex, deviceFrequency);
}
void VorLocalizerWorker::removeVORChannel(int navId)
{
    qDebug("VorLocalizerWorker::removeVORChannel: %d", navId);
    for (int i = 0; i < m_vorChannels.size(); i++)
    {
        if (m_vorChannels[i].m_subChannelId == navId)
        {
            m_vorChannels.removeAt(i);
            break;
        }
    }
    updateChannels();
}
void VorLocalizerWorker::addVORChannel(const VORLocalizerSubChannelSettings& subChannelSettings)
{
    qDebug("VorLocalizerWorker::addVORChannel: %d at %d Hz",
        subChannelSettings.m_id, subChannelSettings.m_frequency);
    VORLocalizerSettings::VORChannel vorChannel =
        VORLocalizerSettings::VORChannel{
            subChannelSettings.m_id,
            subChannelSettings.m_frequency,
            subChannelSettings.m_audioMute
        };
    m_vorChannels.push_back(vorChannel);
    updateChannels();
}
void VorLocalizerWorker::updateChannels()
{
    qDebug() << "VorLocalizerWorker::updateChannels: "
        << "#VORs:" << m_vorChannels.size()
        << "#Chans:" << m_availableChannels->size();
    if ((m_vorChannels.size() == 0) || (m_availableChannels->size() == 0)) {
        return;
    }
    QMutexLocker mlock(&m_mutex);
    std::sort(m_vorChannels.begin(), m_vorChannels.end());
    std::vector devicesChannels;
    getChannelsByDevice(m_availableChannels, devicesChannels);
    QList unallocatedVORs(m_vorChannels);
    m_rrPlans.clear();
    int deviceCount = 0;
    for (auto deviceChannel : devicesChannels)
    {
        unsigned int nbChannels = unallocatedVORs.size() < (int) deviceChannel.m_channels.size() ?
            unallocatedVORs.size() :
            deviceChannel.m_channels.size();
        std::vector vorRanges;
        while (nbChannels != 0)
        {
            getVORRanges(unallocatedVORs, nbChannels, vorRanges);
            filterVORRanges(vorRanges, deviceChannel.m_bandwidth);
            if (vorRanges.size() != 0) {
                break;
            }
            nbChannels--;
        }
        std::vector> vorLists;
        for (auto vorRange : vorRanges)
        {
            QList vorList;
            for (auto index : vorRange.m_vorIndices) {
                vorList.append(VORLocalizerSettings::VORChannel(unallocatedVORs[index]));
            }
            vorLists.push_back(vorList);
        }
        // make one round robin turn for each VOR list for this device
        std::vector rrDevicePlans;
        for (auto vorList : vorLists)
        {
            RRTurnPlan turnPlan(deviceChannel);
            int fMin = vorList.front().m_frequency;
            int fMax = vorList.back().m_frequency;
            int devFreq;
            if (turnPlan.m_fixedCenterFrequency) {
                devFreq = getDeviceCenterFrequency(turnPlan.m_device.m_deviceIndex);
            } else {
                devFreq = (fMin + fMax) / 2;
            }
            turnPlan.m_device.m_frequency = devFreq;
            int iCh = 0;
            // qDebug() << "RR build plan "
            //     << "device:" << turnPlan.m_device.m_deviceIndex
            //     << "freq:" << turnPlan.m_device.m_frequency;
            for (auto vorChannel : vorList)
            {
                RRChannel& channel = turnPlan.m_channels[iCh];
                channel.m_frequencyShift = vorChannel.m_frequency - devFreq;
                channel.m_navId = vorChannel.m_subChannelId;
                // qDebug() << "VOR channel" << vorChannel.m_subChannelId
                //     << "freq:" << vorChannel.m_frequency
                //     << "channel:" << channel.m_channelIndex
                //     << "shift:" << channel.m_frequencyShift;
                // remove VOR from the unallocated list
                QList::iterator it = unallocatedVORs.begin();
                while (it != unallocatedVORs.end())
                {
                    if (it->m_subChannelId == vorChannel.m_subChannelId) {
                        it = unallocatedVORs.erase(it);
                    } else {
                        ++it;
                    }
                }
                iCh++;
            }
            rrDevicePlans.push_back(turnPlan);
        }
        m_rrPlans.push_back(rrDevicePlans);
        deviceCount++;
    }
    qDebug() << "VorLocalizerWorker::updateChannels: unallocatedVORs size:" << unallocatedVORs.size();
    // Fallback for unallocated VORs: add single channel plans for all unallocated VORs
    if ((unallocatedVORs.size() != 0) && (devicesChannels.size() != 0) && m_rrPlans.size() != 0)
    {
        VorLocalizerWorker::RRTurnPlan& deviceChannel = devicesChannels.front();
        std::vector vorRanges;
        getVORRanges(unallocatedVORs, 1, vorRanges);
        std::vector& rrPlan = m_rrPlans.front();
        std::vector> vorLists;
        for (auto vorRange : vorRanges)
        {
            QList vorList;
            for (auto index : vorRange.m_vorIndices) {
                vorList.append(VORLocalizerSettings::VORChannel(unallocatedVORs[index]));
            }
            vorLists.push_back(vorList);
        }
        for (auto vorList : vorLists)
        {
            RRTurnPlan turnPlan(deviceChannel);
            int fMin = vorList.front().m_frequency;
            int fMax = vorList.back().m_frequency;
            int devFreq;
            if (turnPlan.m_fixedCenterFrequency) {
                devFreq = getDeviceCenterFrequency(turnPlan.m_device.m_deviceIndex);
            } else {
                devFreq = (fMin + fMax) / 2;
            }
            turnPlan.m_device.m_frequency = devFreq;
            int iCh = 0;
            // qDebug() << "RR build plan "
            //     << "device:" << turnPlan.m_device.m_deviceIndex
            //     << "freq:" << turnPlan.m_device.m_frequency;
            for (auto vorChannel : vorList)
            {
                RRChannel& channel = turnPlan.m_channels[iCh];
                channel.m_frequencyShift = vorChannel.m_frequency - devFreq;
                channel.m_navId = vorChannel.m_subChannelId;
                // qDebug() << "VOR channel" << vorChannel.m_subChannelId
                //     << "freq:" << vorChannel.m_frequency
                //     << "channel:" << channel.m_channelIndex
                //     << "shift:" << channel.m_frequencyShift;
                iCh++;
            }
            rrPlan.push_back(turnPlan);
        }
    }
    for (auto rrPlans : m_rrPlans)
    {
        qDebug() << "VorLocalizerWorker::updateChannels: RR plans for one device";
        for (auto rrPlan : rrPlans)
        {
            qDebug() << "VorLocalizerWorker::updateChannels:   RR plan: "
                << "device:" << rrPlan.m_device.m_deviceIndex
                << "frequency:" << rrPlan.m_device.m_frequency;
            for (auto rrChannel : rrPlan.m_channels)
            {
                qDebug("VorLocalizerWorker::updateChannels: RR channel: %p index: %d shift: %d navId: %d",
                    rrChannel.m_channelAPI, rrChannel.m_channelIndex, rrChannel.m_frequencyShift, rrChannel.m_navId);
            }
        }
    }
    m_rrTurnCounters.resize(deviceCount);
    std::fill(m_rrTurnCounters.begin(), m_rrTurnCounters.end(), 0);
    rrNextTurn();
}
void VorLocalizerWorker::setChannelShift(int deviceIndex, int channelIndex, double targetOffset, int vorNavId)
{
    SWGSDRangel::SWGChannelSettings channelSettingsResponse;
    SWGSDRangel::SWGErrorResponse errorResponse;
    int httpRC;
    // Get channel settings containg inputFrequencyOffset, so we can patch them
    httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsGet(
        deviceIndex,
        channelIndex,
        channelSettingsResponse,
        errorResponse
    );
    if (httpRC/100 != 2)
    {
        qWarning("VorLocalizerWorker::setChannelShift: get channel offset frequency error %d: %s",
            httpRC, qPrintable(*errorResponse.getMessage()));
    }
    QJsonObject *jsonObj = channelSettingsResponse.asJsonObject();
    if (!WebAPIUtils::setSubObjectDouble(*jsonObj, "inputFrequencyOffset", targetOffset))
    {
        qWarning("VorLocalizerWorker::setChannelShift: No inputFrequencyOffset key in channel settings");
        return;
    }
    if (!WebAPIUtils::setSubObjectInt(*jsonObj, "navId", vorNavId))
    {
        qWarning("VorLocalizerWorker::setChannelShift: No navId key in channel settings");
        return;
    }
    QStringList channelSettingsKeys;
    if (m_settings.m_subChannelSettings.contains(vorNavId))
    {
        if (!WebAPIUtils::setSubObjectInt(*jsonObj, "audioMute", m_settings.m_subChannelSettings[vorNavId].m_audioMute ? 1 : 0)) {
            qWarning("VorLocalizerWorker::setChannelShift: No audioMute key in channel settings");
        } else {
            channelSettingsKeys.append("audioMute");
        }
    }
    channelSettingsKeys.append("inputFrequencyOffset");
    channelSettingsKeys.append("navId");
    channelSettingsResponse.init();
    channelSettingsResponse.fromJsonObject(*jsonObj);
    httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsPutPatch(
        deviceIndex,
        channelIndex,
        false, // PATCH
        channelSettingsKeys,
        channelSettingsResponse,
        errorResponse
    );
    if (httpRC/100 == 2)
    {
        qDebug("VorLocalizerWorker::setChannelShift: inputFrequencyOffset: %f navId: %d OK", targetOffset, vorNavId);
    }
    else
    {
        qWarning("VorLocalizerWorker::setChannelShift: set inputFrequencyOffset and navId error %d: %s",
            httpRC, qPrintable(*errorResponse.getMessage()));
    }
}
void VorLocalizerWorker::setAudioMute(int vorNavId, bool audioMute)
{
    QMutexLocker mlock(&m_mutex);
    if (!m_channelAllocations.contains(vorNavId)) {
        return;
    }
    SWGSDRangel::SWGChannelSettings channelSettingsResponse;
    SWGSDRangel::SWGErrorResponse errorResponse;
    int httpRC;
    int deviceIndex = m_channelAllocations[vorNavId].m_deviceIndex;
    int channelIndex = m_channelAllocations[vorNavId].m_channelIndex;
    // Get channel settings containg inputFrequencyOffset, so we can patch them
    httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsGet(
        deviceIndex,
        channelIndex,
        channelSettingsResponse,
        errorResponse
    );
    if (httpRC/100 != 2)
    {
        qWarning("VorLocalizerWorker::setChannelShift: get channel offset frequency error %d: %s",
            httpRC, qPrintable(*errorResponse.getMessage()));
    }
    QJsonObject *jsonObj = channelSettingsResponse.asJsonObject();
    if (!WebAPIUtils::setSubObjectInt(*jsonObj, "audioMute", audioMute ? 1 : 0))
    {
        qWarning("VorLocalizerWorker::setAudioMute: No audioMute key in channel settings");
        return;
    }
    QStringList channelSettingsKeys;
    channelSettingsKeys.append("audioMute");
    channelSettingsResponse.init();
    channelSettingsResponse.fromJsonObject(*jsonObj);
    httpRC = m_webAPIAdapterInterface->devicesetChannelSettingsPutPatch(
        deviceIndex,
        channelIndex,
        false, // PATCH
        channelSettingsKeys,
        channelSettingsResponse,
        errorResponse
    );
    if (httpRC/100 == 2)
    {
        qDebug("VorLocalizerWorker::setAudioMute: navId: %d audioMute: %d OK", vorNavId, audioMute ? 1 : 0);
    }
    else
    {
        qWarning("VorLocalizerWorker::setAudioMute: navId: %d set audioMute error %d: %s",
            vorNavId, httpRC, qPrintable(*errorResponse.getMessage()));
    }
}
void VorLocalizerWorker::generateIndexCombinations(int length, int subLength, std::vector>& indexCombinations)
{
    indexCombinations.clear();
    std::vector sublist(subLength);
    std::vector::iterator first = sublist.begin(), last = sublist.end();
    std::iota(first, last, 0);
    indexCombinations.push_back(sublist);
    while ((*first) != length - subLength)
    {
        std::vector::iterator mt = last;
        while (*(--mt) == length-(last-mt));
        (*mt)++;
        while (++mt != last) *mt = *(mt-1)+1;
        indexCombinations.push_back(std::vector(first, last));
    }
}
void VorLocalizerWorker::getVORRanges(const QList& vors, int subLength, std::vector& vorRanges)
{
    std::vector> indexCombinations;
    generateIndexCombinations(vors.size(), subLength, indexCombinations);
    vorRanges.clear();
    for (auto indexCombination : indexCombinations)
    {
        int fMax = vors.at(indexCombination.back()).m_frequency;
        int fMin = vors.at(indexCombination.front()).m_frequency;
        vorRanges.push_back(VORRange{indexCombination, fMax - fMin});
    }
}
void VorLocalizerWorker::filterVORRanges(std::vector& vorRanges, int thresholdBW)
{
    std::vector originalVORRanges(vorRanges.size());
    std::copy(vorRanges.begin(), vorRanges.end(), originalVORRanges.begin());
    vorRanges.clear();
    for (auto vorRange : originalVORRanges)
    {
        if (vorRange.m_frequencyRange < thresholdBW) {
            vorRanges.push_back(vorRange);
        }
    }
}
void VorLocalizerWorker::getChannelsByDevice(
    const QHash *availableChannels,
    std::vector& devicesChannels
)
{
    struct
    {
        bool operator()(const RRTurnPlan& a, const RRTurnPlan& b)
        {
            unsigned int nbChannelsA = a.m_channels.size();
            unsigned int nbChannelsB = a.m_channels.size();
            if (nbChannelsA == nbChannelsB) {
                return a.m_bandwidth > b.m_bandwidth;
            } else {
                return nbChannelsA > nbChannelsB;
            }
        }
    } rrTurnPlanGreater;
    QHash::const_iterator itr = availableChannels->begin();
    QMap devicesChannelsMap;
    for (; itr != availableChannels->end(); ++itr)
    {
        devicesChannelsMap[itr->m_deviceSetIndex].m_device.m_deviceIndex = itr->m_deviceSetIndex;
        devicesChannelsMap[itr->m_deviceSetIndex].m_bandwidth = getDeviceSampleRate(itr->m_deviceSetIndex); // Get b/w of device, not channel, as the latter may be decimated
        devicesChannelsMap[itr->m_deviceSetIndex].m_channels.push_back(RRChannel{itr->m_channelAPI, itr->m_channelIndex, 0, -1});
    }
    QMap::iterator itm = devicesChannelsMap.begin();
    devicesChannels.clear();
    for (; itm != devicesChannelsMap.end(); ++itm)
    {
        itm->m_fixedCenterFrequency = hasCenterFrequencySetting(itm->m_device.m_deviceIndex);
        devicesChannels.push_back(*itm);
    }
    std::sort(devicesChannels.begin(), devicesChannels.end(), rrTurnPlanGreater);
}
void VorLocalizerWorker::rrNextTurn()
{
    QMutexLocker mlock(&m_mutex);
    int iDevPlan = 0;
    VORLocalizerReport::MsgReportServiceddVORs *msg = VORLocalizerReport::MsgReportServiceddVORs::create();
    m_channelAllocations.clear();
    for (auto rrPlan : m_rrPlans)
    {
        unsigned int turnCount = m_rrTurnCounters[iDevPlan];
        int deviceIndex = rrPlan[turnCount].m_device.m_deviceIndex;
        int deviceFrequency = rrPlan[turnCount].m_device.m_frequency - m_settings.m_centerShift;
        qDebug() << "VorLocalizerWorker::rrNextTurn: "
            << "turn:" << turnCount
            << "device:" << deviceIndex
            << "frequency:" << deviceFrequency - m_settings.m_centerShift;
        if (!rrPlan[turnCount].m_fixedCenterFrequency) {
            ChannelWebAPIUtils::setCenterFrequency(deviceIndex, deviceFrequency);
        }
        for (auto channel : rrPlan[turnCount].m_channels)
        {
            int shift = channel.m_frequencyShift;
            if (!rrPlan[turnCount].m_fixedCenterFrequency) {
                shift += m_settings.m_centerShift;
            }
            qDebug() << "VorLocalizerWorker::rrNextTurn: "
                << "device:" << deviceIndex
                << "channel:" << channel.m_channelIndex
                << "shift:" << shift
                << "navId:" << channel.m_navId;
            setChannelShift(
                deviceIndex,
                channel.m_channelIndex,
                shift,
                channel.m_navId
            );
            m_channelAllocations[channel.m_navId] = ChannelAllocation{
                channel.m_navId,
                deviceIndex,
                channel.m_channelIndex
            };
            if(m_availableChannels->contains(channel.m_channelAPI))
            {
                VORLocalizerSettings::AvailableChannel& availableChannel = m_availableChannels->operator[](channel.m_channelAPI);
                availableChannel.m_navId = channel.m_navId;
            }
            msg->getNavIds().push_back(channel.m_navId);
            msg->getSinglePlans()[channel.m_navId] = (rrPlan.size() == 1);
        }
        turnCount++;
        if (turnCount == rrPlan.size()) {
            turnCount = 0;
        }
        m_rrTurnCounters[iDevPlan] = turnCount;
        iDevPlan++;
    }
    if (m_msgQueueToFeature) {
        m_msgQueueToFeature->push(msg);
    }
}