// SPDX-License-Identifier: GPL-2.0-only
/*
 * Digital Voice Modem - Bridge
 * GPLv2 Open Source. Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 *  Copyright (C) 2026 Bryan Biedenkapp, N2PLL
 *
 */
#include "Defines.h"
#include "common/analog/AnalogDefines.h"
#include "common/analog/AnalogAudio.h"
#include "common/dmr/DMRDefines.h"
#include "common/edac/AMBEFEC.h"
#include "common/nxdn/NXDNDefines.h"
#include "common/nxdn/NXDNUtils.h"
#include "common/nxdn/Audio.h"
#include "common/nxdn/Sync.h"
#include "common/nxdn/channel/FACCH1.h"
#include "common/nxdn/channel/LICH.h"
#include "common/nxdn/channel/SACCH.h"
#include "common/nxdn/lc/RTCH.h"
#include "common/Log.h"
#include "common/Utils.h"
#include "bridge/ActivityLog.h"
#include "HostBridge.h"
#include "BridgeMain.h"

using namespace analog;
using namespace analog::defines;
using namespace network;
using namespace network::frame;
using namespace network::udp;

#include <cstdio>
#include <algorithm>
#include <functional>
#include <random>

// ---------------------------------------------------------------------------
//  Public Class Members
// ---------------------------------------------------------------------------

/*
** NXDN
*/

/* Helper to process NXDN network traffic. */

void HostBridge::processNXDNNetwork(uint8_t* buffer, uint32_t length)
{
    assert(buffer != nullptr);
    using namespace nxdn;
    using namespace nxdn::defines;

    if (m_txMode != TX_MODE_NXDN) {
        m_network->resetNXDN();
        return;
    }

    uint8_t messageType = buffer[4U];
    uint32_t srcId = GET_UINT24(buffer, 5U);
    uint32_t dstId = GET_UINT24(buffer, 8U);

    if (m_debug) {
        LogDebug(LOG_NET, "NXDN, messageType = $%02X, srcId = %u, dstId = %u, len = %u", messageType, srcId, dstId, length);
    }

    if (m_audioDetect || m_trafficFromUDP)
        return;

    if (srcId == 0U) {
        m_network->resetNXDN();
        return;
    }

    if (dstId != m_dstId) {
        m_network->resetNXDN();
        return;
    }

    uint8_t frameLength = buffer[23U];
    if (frameLength == 0U) {
        m_network->resetNXDN();
        return;
    }

    uint32_t payloadLength = frameLength;
    if (payloadLength > NXDN_FRAME_LENGTH_BYTES && payloadLength >= 24U)
        payloadLength -= 24U;

    if (payloadLength < NXDN_FRAME_LENGTH_BYTES) {
        m_network->resetNXDN();
        return;
    }

    uint8_t frame[NXDN_FRAME_LENGTH_BYTES + 2U];
    ::memset(frame, 0x00U, NXDN_FRAME_LENGTH_BYTES + 2U);
    ::memcpy(frame + 2U, buffer + 24U, NXDN_FRAME_LENGTH_BYTES);

    NXDNUtils::scrambler(frame + 2U);

    channel::LICH lich;
    if (!lich.decode(frame + 2U)) {
        m_network->resetNXDN();
        return;
    }

    FuncChannelType::E fct = lich.getFCT();

    // process non-superframe control signalling (VCALL/TX_REL) from FACCH
    if (fct == FuncChannelType::USC_SACCH_NS) {
        channel::FACCH1 facch;
        bool valid = facch.decode(frame + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
        if (!valid) {
            valid = facch.decode(frame + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS);
        }

        if (valid) {
            uint8_t lcBuffer[10U];
            ::memset(lcBuffer, 0x00U, 10U);
            facch.getData(lcBuffer);

            lc::RTCH lc;
            lc.decode(lcBuffer, NXDN_FACCH1_FEC_LENGTH_BITS);

            if (lc.getMessageType() == MessageType::RTCH_VCALL) {
                m_rxNXDNLC = lc;

                m_networkWatchdog.start();

                if (m_network->getNXDNStreamId() != m_rxStreamId && !m_callInProgress) {
                    m_callInProgress = true;
                    m_callAlgoId = 0U;

                    uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
                    m_rxStartTime = now;

                    LogInfoEx(LOG_HOST, "NXDN, call start, srcId = %u, dstId = %u", srcId, dstId);
                    if (m_preambleLeaderTone)
                        generatePreambleTone();
                }
            }
            else if (lc.getMessageType() == MessageType::RTCH_TX_REL || lc.getMessageType() == MessageType::RTCH_TX_REL_EX) {
                m_callInProgress = false;
                m_networkWatchdog.stop();
                m_ignoreCall = false;
                m_callAlgoId = 0U;

                if (m_rxStartTime > 0U) {
                    uint64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
                    uint64_t diff = now - m_rxStartTime;

                    LogInfoEx(LOG_HOST, "NXDN, call end, srcId = %u, dstId = %u, dur = %us", srcId, dstId, diff / 1000U);
                }

                m_rxNXDNLC = lc::RTCH();
                m_rxStartTime = 0U;
                m_rxStreamId = 0U;

                if (!m_udpRTPContinuousSeq) {
                    m_rtpInitialFrame = false;
                    m_rtpSeqNo = 0U;
                }
                m_rtpTimestamp = INVALID_TS;
                m_network->resetNXDN();
                return;
            }
        }
    }

    if (m_ignoreCall)
        return;

    LogInfoEx(LOG_NET, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u", srcId, dstId);
    decodeNXDNAudioFrame(frame, srcId, dstId, m_nxdnSeqNo);

    m_nxdnSeqNo++;
    m_rxStreamId = m_network->getNXDNStreamId();
}

/* Helper to decode NXDN network traffic audio frames. */

void HostBridge::decodeNXDNAudioFrame(uint8_t* frame, uint32_t srcId, uint32_t dstId, uint8_t nxdnN)
{
    assert(frame != nullptr);
    using namespace nxdn;
    using namespace nxdn::defines;

    channel::LICH lich;
    if (!lich.decode(frame + 2U))
        return;

    ChOption::E option = lich.getOption();

    nxdn::Audio nxdnAudio;
    ::edac::AMBEFEC ambeFec;

    // ahhh yes fantastical C++ lambda functions...
    auto decodePair = [&](uint32_t pairOffset, uint8_t vcBase) {
        uint8_t nxdnAMBE[18U];
        ::memset(nxdnAMBE, 0x00U, 18U);
        ::memcpy(nxdnAMBE, frame + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + pairOffset, 18U);

        ambeFec.regenerateNXDN(nxdnAMBE + 0U);
        ambeFec.regenerateNXDN(nxdnAMBE + 9U);

        uint8_t packedBits[13U];
        ::memset(packedBits, 0x00U, 13U);
        nxdnAudio.decode(nxdnAMBE, packedBits);

        for (uint8_t half = 0U; half < 2U; half++) {
            uint8_t rawBits[72U];
            ::memset(rawBits, 0x00U, 72U);

            for (uint32_t b = 0U; b < 49U; b++) {
                rawBits[b] = READ_BIT(packedBits, (half * 49U) + b) ? 1U : 0U;
            }

            // HACK: use the DMR AMBE handling to decode NXDN audio
            uint8_t dmrAMBE[dmr::defines::DMR_AMBE_LENGTH_BYTES];
            ::memset(dmrAMBE, 0x00U, dmr::defines::DMR_AMBE_LENGTH_BYTES);
            m_encoder->encodeBits(rawBits, dmrAMBE);

            short samples[AUDIO_SAMPLES_LENGTH];
            int errs = 0;
#if defined(_WIN32)
            if (m_useExternalVocoder) {
                ambeDecode(dmrAMBE, dmr::defines::DMR_AMBE_LENGTH_BYTES, samples);
            }
            else {
#endif // defined(_WIN32)
                errs = m_decoder->decode(dmrAMBE, samples);
#if defined(_WIN32)
            }
#endif // defined(_WIN32)

            if (m_debug) {
                LogDebug(LOG_HOST, "NXDN, Frame, VC%u.%u, srcId = %u, dstId = %u, errs = %u", nxdnN, vcBase + half, srcId, dstId, errs);
            }

            AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_rxAudioGain);

            if (m_localAudio) {
                m_outputAudio.addData(samples, AUDIO_SAMPLES_LENGTH);
                assertRtsPtt();
            }

            if (m_udpAudio) {
                int pcmIdx = 0;
                uint8_t pcm[AUDIO_SAMPLES_LENGTH * 2U];
                if (m_udpUseULaw) {
                    for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) {
                        pcm[smpIdx] = AnalogAudio::encodeMuLaw(samples[smpIdx]);
                    }

                    if (m_trace)
                        Utils::dump(1U, "HostBridge()::decodeNXDNAudioFrame(), Encoded uLaw Audio", pcm, AUDIO_SAMPLES_LENGTH);

                    writeUDPAudio(srcId, dstId, pcm, AUDIO_SAMPLES_LENGTH_BYTES / 2U);
                }
                else {
                    for (uint32_t smpIdx = 0; smpIdx < AUDIO_SAMPLES_LENGTH; smpIdx++) {
                        pcm[pcmIdx + 0] = (uint8_t)(samples[smpIdx] & 0xFF);
                        pcm[pcmIdx + 1] = (uint8_t)((samples[smpIdx] >> 8) & 0xFF);
                        pcmIdx += 2;
                    }

                    writeUDPAudio(srcId, dstId, pcm, AUDIO_SAMPLES_LENGTH_BYTES);
                }
            }
        }
    };

    switch (option) {
    case ChOption::STEAL_NONE:
        decodePair(0U, 0U);
        decodePair(18U, 2U);
        break;
    case ChOption::STEAL_FACCH1_1:
        decodePair(18U, 2U);
        break;
    case ChOption::STEAL_FACCH1_2:
        decodePair(0U, 0U);
        break;
    case ChOption::STEAL_FACCH:
    default:
        break;
    }
}

/* Helper to encode NXDN network traffic audio frames. */

void HostBridge::encodeNXDNAudioFrame(uint8_t* pcm, uint32_t forcedSrcId, uint32_t forcedDstId)
{
    assert(pcm != nullptr);
    using namespace nxdn;
    using namespace nxdn::defines;

    uint32_t srcId = m_srcId;
    if (m_srcIdOverride != 0 && (m_overrideSrcIdFromMDC))
        srcId = m_srcIdOverride;
    if (m_overrideSrcIdFromUDP)
        srcId = m_udpSrcId;
    if (forcedSrcId > 0 && forcedSrcId != m_srcId)
        srcId = forcedSrcId;

    uint32_t dstId = m_dstId;
    if (forcedDstId > 0 && forcedDstId != m_dstId)
        dstId = forcedDstId;

    if (srcId == 0U)
        srcId = m_srcId;

    int smpIdx = 0;
    short samples[AUDIO_SAMPLES_LENGTH];
    for (uint32_t pcmIdx = 0; pcmIdx < (AUDIO_SAMPLES_LENGTH * 2U); pcmIdx += 2) {
        samples[smpIdx] = (short)((pcm[pcmIdx + 1] << 8) + pcm[pcmIdx + 0]);
        smpIdx++;
    }

    AnalogAudio::gain(samples, AUDIO_SAMPLES_LENGTH, m_txAudioGain);

    // HACK: use the DMR AMBE handling to encode NXDN audio
    uint8_t dmrAMBE[dmr::defines::DMR_AMBE_LENGTH_BYTES];
    ::memset(dmrAMBE, 0x00U, dmr::defines::DMR_AMBE_LENGTH_BYTES);
#if defined(_WIN32)
    if (m_useExternalVocoder) {
        ambeEncode(samples, AUDIO_SAMPLES_LENGTH, dmrAMBE);
    }
    else {
#endif // defined(_WIN32)
        m_encoder->encode(samples, dmrAMBE);
#if defined(_WIN32)
    }
#endif // defined(_WIN32)

    if (m_nxdnN >= 4U) {
        m_nxdnN = 0U;
    }

    ::memcpy(m_nxdnAMBE + (m_nxdnN * dmr::defines::DMR_AMBE_LENGTH_BYTES), dmrAMBE, dmr::defines::DMR_AMBE_LENGTH_BYTES);
    m_nxdnN++;

    if (m_nxdnN < 4U) {
        return;
    }

    nxdn::Audio nxdnAudio;

    uint8_t nxdnAudioPayload[36U];
    ::memset(nxdnAudioPayload, 0x00U, 36U);

    for (uint8_t pair = 0U; pair < 2U; pair++) {
        uint8_t packedBits[13U];
        ::memset(packedBits, 0x00U, 13U);

        for (uint8_t half = 0U; half < 2U; half++) {
            char mbeBits[49U];
            ::memset(mbeBits, 0x00U, 49U);

            uint8_t* ambe = m_nxdnAMBE + ((pair * 2U + half) * dmr::defines::DMR_AMBE_LENGTH_BYTES);
            m_decoder->decodeBits(ambe, mbeBits);

            for (uint32_t b = 0U; b < 49U; b++) {
                WRITE_BIT(packedBits, (half * 49U) + b, mbeBits[b] != 0);
            }
        }

        uint8_t encodedPair[18U];
        ::memset(encodedPair, 0x00U, 18U);
        nxdnAudio.encode(packedBits, encodedPair);
        ::memcpy(nxdnAudioPayload + (pair * 18U), encodedPair, 18U);
    }

    lc::RTCH lc = lc::RTCH();
    lc.setMessageType(MessageType::RTCH_VCALL);
    lc.setCallType(CallType::UNSPECIFIED);
    lc.setGroup(true);
    lc.setSrcId((uint16_t)srcId);
    lc.setDstId((uint16_t)dstId);
    lc.setTransmissionMode(TransmissionMode::MODE_4800);

    if (m_nxdnSeqNo == 0U) {
        uint8_t controlFrame[NXDN_FRAME_LENGTH_BYTES + 2U];
        ::memset(controlFrame, 0x00U, NXDN_FRAME_LENGTH_BYTES + 2U);

        Sync::addNXDNSync(controlFrame + 2U);

        channel::LICH lich;
        lich.setRFCT(RFChannelType::RDCH);
        lich.setFCT(FuncChannelType::USC_SACCH_NS);
        lich.setOption(ChOption::STEAL_FACCH);
        lich.setOutbound(true);
        lich.encode(controlFrame + 2U);

        channel::SACCH sacch;
        sacch.setData(SACCH_IDLE);
        sacch.setRAN(0U);
        sacch.setStructure(ChStructure::SR_SINGLE);
        sacch.encode(controlFrame + 2U);

        channel::FACCH1 facch;
        uint8_t lcData[NXDN_RTCH_LC_LENGTH_BYTES];
        ::memset(lcData, 0x00U, NXDN_RTCH_LC_LENGTH_BYTES);
        lc.encode(lcData, NXDN_RTCH_LC_LENGTH_BITS);
        facch.setData(lcData);
        facch.encode(controlFrame + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS);
        facch.encode(controlFrame + 2U, NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_SACCH_FEC_LENGTH_BITS + NXDN_FACCH1_FEC_LENGTH_BITS);

        NXDNUtils::scrambler(controlFrame + 2U);

        LogInfoEx(LOG_HOST, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", srcId = %u, dstId = %u", srcId, dstId);
        m_network->writeNXDN(lc, controlFrame + 2U, NXDN_FRAME_LENGTH_BYTES);
        m_txStreamId = m_network->getNXDNStreamId();
    }

    uint8_t voiceFrame[NXDN_FRAME_LENGTH_BYTES + 2U];
    ::memset(voiceFrame, 0x00U, NXDN_FRAME_LENGTH_BYTES + 2U);

    Sync::addNXDNSync(voiceFrame + 2U);

    channel::LICH lich;
    lich.setRFCT(RFChannelType::RDCH);
    lich.setFCT(FuncChannelType::USC_SACCH_SS);
    lich.setOption(ChOption::STEAL_NONE);
    lich.setOutbound(true);
    lich.encode(voiceFrame + 2U);

    channel::SACCH sacch;
    sacch.setData(SACCH_IDLE);
    sacch.setRAN(0U);
    sacch.setStructure(ChStructure::SR_SINGLE);
    sacch.encode(voiceFrame + 2U);

    ::memcpy(voiceFrame + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES, nxdnAudioPayload, 36U);

    NXDNUtils::scrambler(voiceFrame + 2U);

    LogInfoEx(LOG_HOST, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL ", audio, srcId = %u, dstId = %u", srcId, dstId);
    m_network->writeNXDN(lc, voiceFrame + 2U, NXDN_FRAME_LENGTH_BYTES);
    m_txStreamId = m_network->getNXDNStreamId();

    m_nxdnSeqNo++;
    m_nxdnN = 0U;
    ::memset(m_nxdnAMBE, 0x00U, 36U);
}
