///////////////////////////////////////////////////////////////////////////////////
// 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 <QDebug>

#include <complex.h>

#include "dsp/scopevis.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "channel/channelwebapiutils.h"

#include "dscdemod.h"
#include "dscdemodsink.h"

DSCDemodSink::DSCDemodSink(DSCDemod *dscDemod) :
        m_scopeSink(nullptr),
        m_dscDemod(dscDemod),
        m_channelSampleRate(DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE),
        m_channelFrequencyOffset(0),
        m_magsqSum(0.0f),
        m_magsqPeak(0.0f),
        m_magsqCount(0),
        m_messageQueueToChannel(nullptr),
        m_exp(nullptr),
        m_sampleBufferIndex(0)
{
    m_magsq = 0.0;

    for (int i = 0; i < DSCDemodSettings::m_scopeStreams; i++) {
        m_sampleBuffer[i].resize(m_sampleBufferSize);
    }

    applySettings(QStringList(), m_settings, true);
    applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);

    m_lowpassComplex1.create(301, DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE, DSCDemodSettings::DSCDEMOD_BAUD_RATE * 1.1);
    m_lowpassComplex2.create(301, DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE, DSCDemodSettings::DSCDEMOD_BAUD_RATE * 1.1);
}

DSCDemodSink::~DSCDemodSink()
{
    delete[] m_exp;
}

void DSCDemodSink::sampleToScope(Complex sample, Real abs1Filt, Real abs2Filt, Real unbiasedData, Real biasedData)
{
    if (m_scopeSink)
    {
        m_sampleBuffer[0][m_sampleBufferIndex] = sample;
        m_sampleBuffer[1][m_sampleBufferIndex] = Complex(m_magsq, 0.0f);
        m_sampleBuffer[2][m_sampleBufferIndex] = Complex(abs1Filt, 0.0f);
        m_sampleBuffer[3][m_sampleBufferIndex] = Complex(abs2Filt, 0.0f);
        m_sampleBuffer[4][m_sampleBufferIndex] = Complex(unbiasedData, 0.0f);
        m_sampleBuffer[5][m_sampleBufferIndex] = Complex(biasedData, 0.0f);
        m_sampleBuffer[6][m_sampleBufferIndex] = Complex(m_data, 0.0f);
        m_sampleBuffer[7][m_sampleBufferIndex] = Complex(m_clock, 0.0f);
        m_sampleBuffer[8][m_sampleBufferIndex] = Complex(m_bit, 0.0f);
        m_sampleBuffer[9][m_sampleBufferIndex] = Complex(m_gotSOP, 0.0f);
        m_sampleBufferIndex++;

        if (m_sampleBufferIndex == m_sampleBufferSize)
        {
            std::vector<ComplexVector::const_iterator> vbegin;

            for (int i = 0; i < DSCDemodSettings::m_scopeStreams; i++) {
                vbegin.push_back(m_sampleBuffer[i].begin());
            }

            m_scopeSink->feed(vbegin, m_sampleBufferSize);
            m_sampleBufferIndex = 0;
        }
    }
}

void DSCDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
    Complex ci;

    for (SampleVector::const_iterator it = begin; it != end; ++it)
    {
        Complex c(it->real(), it->imag());
        c *= m_nco.nextIQ();

        if (m_interpolatorDistance < 1.0f) // interpolate
        {
            while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
            {
                processOneSample(ci);
                m_interpolatorDistanceRemain += m_interpolatorDistance;
            }
        }
        else // decimate
        {
            if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
            {
                processOneSample(ci);
                m_interpolatorDistanceRemain += m_interpolatorDistance;
            }
        }
    }
}

void DSCDemodSink::processOneSample(Complex &ci)
{
    // Calculate average and peak levels for level meter
    double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag();;
    Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
    m_movingAverage(magsq);
    m_magsq = m_movingAverage.asDouble();
    m_magsqSum += magsq;
    if (magsq > m_magsqPeak)
    {
        m_magsqPeak = magsq;
    }
    m_magsqCount++;

    // Sum power while data is being received
    if (m_gotSOP)
    {
        m_rssiMagSqSum += magsq;
        m_rssiMagSqCount++;
    }

    ci /= SDR_RX_SCALEF;

    // Correlate with expected frequencies
    Complex exp = m_exp[m_expIdx];
    m_expIdx = (m_expIdx + 1) % m_expLength;
    Complex corr1 = ci * exp;
    Complex corr2 = ci * std::conj(exp);

    // Low pass filter
    Real abs1Filt = std::abs(m_lowpassComplex1.filter(corr1));
    Real abs2Filt = std::abs(m_lowpassComplex2.filter(corr2));

    // Envelope calculation
    m_movMax1(abs1Filt);
    m_movMax2(abs2Filt);
    Real env1 = m_movMax1.getMaximum();
    Real env2 = m_movMax2.getMaximum();

    // Automatic threshold correction to compensate for frequency selective fading
    // http://www.w7ay.net/site/Technical/ATC/index.html
    Real bias1 = abs1Filt - 0.5 * env1;
    Real bias2 = abs2Filt - 0.5 * env2;
    Real unbiasedData = abs1Filt - abs2Filt;
    Real biasedData = bias1 - bias2;

    // Save current data for edge detection
    m_dataPrev = m_data;
    // Set data according to strongest correlation
    m_data = biasedData > 0;

    // Calculate timing error (we expect clockCount to be 0 when data changes), and add a proportion of it
    if (m_data && !m_dataPrev) {
        m_clockCount -= m_clockCount * 0.25;
    }

    m_clockCount += 1.0;
    if (m_clockCount >= m_samplesPerBit/2.0-1.0)
    {
        // Sample in middle of symbol
        receiveBit(m_data);
        m_clock = 1;
        // Wrap clock counter
        m_clockCount -= m_samplesPerBit;
    }
    else
    {
        m_clock = 0;
    }

    sampleToScope(ci, abs1Filt, abs2Filt, unbiasedData, biasedData);
}

const QList<DSCDemodSink::PhasingPattern> DSCDemodSink::m_phasingPatterns = {
    {0b1011111001'1111011001'1011111001, 9},      // 125 111 125
    {0b1111011001'1011111001'0111011010, 8},      // 111 125 110
    {0b1011111001'0111011010'1011111001, 7},      // 125 110 125
    {0b0111011010'1011111001'1011011010, 6},      // 110 125 109
    {0b1011111001'1011011010'1011111001, 5},      // 125 109 125
    {0b1011011010'1011111001'0011011011, 4},      // 109 125 108
    {0b1011111001'0011011011'1011111001, 3},      // 125 108 125
    {0b0011011011'1011111001'1101011010, 2},      // 108 125 107
    {0b1011111001'1101011010'1011111001, 1},      // 125 107 125
    {0b1101011010'1011111001'0101011011, 0},      // 107 125 106
};

void DSCDemodSink::receiveBit(bool bit)
{
    m_bit = bit;

    // Store in shift reg
    m_bits = (m_bits << 1) | m_bit;
    m_bitCount++;

    if (!m_gotSOP)
    {
        // Dot pattern - 200 1/0s or 20 1/0s
        // Phasing pattern - 6 DX=125 RX=111 110 109 108 107 106 105 104
        // Phasing is considered to be achieved when two DXs and one RX, or two RXs and one DX, or three RXs in the appropriate DX or RX positions, respectively, are successfully received.
        if (m_bitCount == 10*3)
        {
            m_bitCount--;

            unsigned int pat = m_bits & 0x3fffffff;
            for (int i = 0; i < m_phasingPatterns.size(); i++)
            {
                if (pat == m_phasingPatterns[i].m_pattern)
                {
                    m_dscDecoder.init(m_phasingPatterns[i].m_offset);
                    m_gotSOP = true;
                    m_bitCount = 0;
                    m_rssiMagSqSum = 0.0;
                    m_rssiMagSqCount = 0;
                    break;
                }
            }
        }
    }
    else
    {
        if (m_bitCount == 10)
        {
            if (m_dscDecoder.decodeBits(m_bits & 0x3ff))
            {
                QDateTime dateTime = QDateTime::currentDateTime();

                if (m_settings.m_useFileTime)
                {
                    QString hardwareId = m_dscDemod->getDeviceAPI()->getHardwareId();

                    if ((hardwareId == "FileInput") || (hardwareId == "SigMFFileInput"))
                    {
                        QString dateTimeStr;
                        int deviceIdx = m_dscDemod->getDeviceSetIndex();

                        if (ChannelWebAPIUtils::getDeviceReportValue(deviceIdx, "absoluteTime", dateTimeStr)) {
                            dateTime = QDateTime::fromString(dateTimeStr, Qt::ISODateWithMs);
                        }
                    }
                }

                QByteArray bytes = m_dscDecoder.getMessage();
                DSCMessage message(bytes, dateTime);
                //qDebug() << "RX Bytes: " << bytes.toHex();
                //qDebug() << "DSC Message: " << message.toString();

                if (getMessageQueueToChannel())
                {
                    float rssi = CalcDb::dbPower(m_rssiMagSqSum / m_rssiMagSqCount);
                    DSCDemod::MsgMessage *msg = DSCDemod::MsgMessage::create(message, m_dscDecoder.getErrors(), rssi);
                    getMessageQueueToChannel()->push(msg);
                }

                // Reset demod
                init();
            }
            m_bitCount = 0;
        }
    }
}

void DSCDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
    qDebug() << "DSCDemodSink::applyChannelSettings:"
            << " channelSampleRate: " << channelSampleRate
            << " channelFrequencyOffset: " << channelFrequencyOffset;

    if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
        (m_channelSampleRate != channelSampleRate) || force)
    {
        m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
    }

    if ((m_channelSampleRate != channelSampleRate) || force)
    {
        m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2);
        m_interpolatorDistance = (Real) channelSampleRate / (Real) DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE;
        m_interpolatorDistanceRemain = m_interpolatorDistance;
    }

    m_channelSampleRate = channelSampleRate;
    m_channelFrequencyOffset = channelFrequencyOffset;
}

void DSCDemodSink::init()
{
    m_expIdx = 0;
    m_bit = 0;
    m_bits = 0;
    m_bitCount = 0;
    m_gotSOP = false;
    m_errorCount = 0;
    m_clockCount = -m_samplesPerBit/2.0;
    m_clock = 0;
    m_int = 0.0;
    m_rssiMagSqSum = 0.0;
    m_rssiMagSqCount = 0;
    m_consecutiveErrors = 0;
    m_messageBuffer = "";
}

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

    if ((settingsKeys.contains("rfBandwidth") && (settings.m_rfBandwidth != m_settings.m_rfBandwidth)) || force)
    {
        m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2);
        m_interpolatorDistance = (Real) m_channelSampleRate / (Real) DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE;
        m_interpolatorDistanceRemain = m_interpolatorDistance;
    }

    if (force)
    {
        delete[] m_exp;
        m_exp = new Complex[m_expLength];
        Real f0 = 0.0f;
        for (int i = 0; i < m_expLength; i++)
        {
            m_exp[i] = Complex(cos(f0), sin(f0));
            f0 += 2.0f * (Real)M_PI * (DSCDemodSettings::DSCDEMOD_FREQUENCY_SHIFT/2.0f) / DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE;
        }
        init();

        m_movMax1.setSize(m_samplesPerBit * 8);
        m_movMax2.setSize(m_samplesPerBit * 8);
        m_settings = settings;
    }
    else
    {
        m_settings.applySettings(settingsKeys, settings);
    }
}
