// SPDX-License-Identifier: PROPRIETARY
/*
 * Digital Voice Modem - FNE System View
 * GPLv2 Open Source. Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 *  Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL
 *
 */
#include "sysview/Defines.h"
#include "common/p25/dfsi/DFSIDefines.h"
#include "common/p25/dfsi/LC.h"
#include "common/json/json.h"
#include "common/zlib/zlib.h"
#include "common/Utils.h"
#include "network/PeerNetwork.h"
#include "SysViewMain.h"

using namespace network;

#include <cassert>
#include <fstream>
#include <streambuf>

// ---------------------------------------------------------------------------
//  Constants
// ---------------------------------------------------------------------------

const uint32_t PEER_STATUS_EXPIRY = 10U; // 10 seconds

// ---------------------------------------------------------------------------
//  Static Class Members
// ---------------------------------------------------------------------------

std::mutex PeerNetwork::s_peerStatusMutex;

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

/* Initializes a new instance of the PeerNetwork class. */

PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t localPort, uint32_t peerId, const std::string& password,
    bool duplex, bool debug, bool allowActivityTransfer, bool allowDiagnosticTransfer, bool updateLookup, bool saveLookup) :
    Network(address, port, localPort, peerId, password, duplex, debug, true, true, true, true, true, true, allowActivityTransfer, allowDiagnosticTransfer, updateLookup, saveLookup),
    peerStatus(),
    peerStatusTimers(),
    m_peerReplica(false),
    m_tgidPkt(true, "Peer Replication, TGID List"),
    m_ridPkt(true, "Peer Replication, RID List")
{
    assert(!address.empty());
    assert(port > 0U);
    assert(!password.empty());

    // ignore the source peer ID for packets destined to SysView
    m_promiscuousPeer = true;
}

/* Gets the current DMR stream ID. */

uint32_t PeerNetwork::getRxDMRStreamId(uint32_t slotNo) const
{
    assert(slotNo == 1U || slotNo == 2U);

    if (slotNo == 1U) {
        return m_rxDMRStreamId[0U];
    }
    else {
        return m_rxDMRStreamId[1U];
    }
}

/* Gets the current P25 Phase 2 stream ID. */

uint32_t PeerNetwork::getRxP25P2StreamId(uint32_t slotNo) const
{
    assert(slotNo == 1U || slotNo == 2U);

    if (slotNo == 1U) {
        return m_rxP25P2StreamId[0U];
    }
    else {
        return m_rxP25P2StreamId[1U];
    }
}

// ---------------------------------------------------------------------------
//  Protected Class Members
// ---------------------------------------------------------------------------

/* User overrideable handler that allows user code to process network packets not handled by this class. */

void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId,
    const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader)
{
    switch (opcode.first) {
    case NET_FUNC::TRANSFER:
    {
        switch (opcode.second) {
        case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY:
        {
            DECLARE_UINT8_ARRAY(rawPayload, length - 11U);
            ::memcpy(rawPayload, data + 11U, length - 11U);
            std::string payload(rawPayload, rawPayload + (length - 11U));

            bool currState = g_disableTimeDisplay;
            g_disableTimeDisplay = true;

            std::string identity = std::string();
            auto it = std::find_if(g_peerIdentityNameMap.begin(), g_peerIdentityNameMap.end(), [&](PeerIdentityMapPair x) { return x.first == peerId; });
            if (it != g_peerIdentityNameMap.end())
                identity = g_peerIdentityNameMap[peerId];

            ::Log(9999U, {nullptr, nullptr, 0U, nullptr}, "%.9u (%8s) %s", peerId, identity.c_str(), payload.c_str());
            g_disableTimeDisplay = currState;
        }
        break;

        case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS:
        {
            DECLARE_UINT8_ARRAY(rawPayload, length - 11U);
            ::memcpy(rawPayload, data + 11U, length - 11U);
            std::string payload(rawPayload, rawPayload + (length - 11U));

            if (g_debug)
                LogInfoEx(LOG_NET, "Peer Status, peerId = %u", peerId);

            // parse JSON body
            json::value v;
            std::string err = json::parse(v, payload);
            if (!err.empty()) {
                break;
            }

            // ensure parsed JSON is an object
            if (!v.is<json::object>()) {
                break;
            }

            json::object obj = v.get<json::object>();
            uint32_t actualPeerId = obj["peerId"].getDefault<uint32_t>(peerId);
            std::lock_guard<std::mutex> lock(s_peerStatusMutex);
            peerStatus[actualPeerId] = obj;
            peerStatusTimers[actualPeerId].start(PEER_STATUS_EXPIRY);
        }
        break;

        default:
            break;
        }
    }
    break;
    
    case NET_FUNC::REPL:
    {
        switch (opcode.second) {
        case NET_SUBFUNC::REPL_TALKGROUP_LIST:
        {
            uint32_t decompressedLen = 0U;
            uint8_t* decompressed = nullptr;

            if (m_tgidPkt.decode(data, &decompressed, &decompressedLen)) {
                if (m_tidLookup == nullptr) {
                    LogError(LOG_NET, "Talkgroup ID lookups not available yet.");
                    m_tgidPkt.clear();
                    delete[] decompressed;
                    break;
                }

                // store to file
                DECLARE_CHAR_ARRAY(str, decompressedLen + 1U);
                ::memcpy(str, decompressed, decompressedLen);
                str[decompressedLen] = 0; // null termination

                // randomize filename
                std::ostringstream s;
                std::random_device rd;
                std::mt19937 mt(rd());
                std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
                s << "/tmp/talkgroup_rules.yml." << dist(mt);

                std::string filename = s.str();
                std::ofstream file(filename, std::ofstream::out);
                if (file.fail()) {
                    LogError(LOG_NET, "Cannot open the talkgroup ID lookup file - %s", filename.c_str());
                    m_tgidPkt.clear();
                    delete[] decompressed;
                    break;
                }

                file << str;
                file.close();

                m_tidLookup->stop(true);
                m_tidLookup->filename(filename);
                m_tidLookup->reload();

                // flag this peer as replica enabled
                m_peerReplica = true;

                // cleanup temporary file
                ::remove(filename.c_str());
                m_tgidPkt.clear();
                delete[] decompressed;
            }
        }
        break;

        case NET_SUBFUNC::REPL_RID_LIST:
        {
            uint32_t decompressedLen = 0U;
            uint8_t* decompressed = nullptr;

            if (m_ridPkt.decode(data, &decompressed, &decompressedLen)) {
                if (m_ridLookup == nullptr) {
                    LogError(LOG_NET, "Radio ID lookups not available yet.");
                    m_ridPkt.clear();
                    delete[] decompressed;
                    break;
                }

                // store to file
                DECLARE_CHAR_ARRAY(str, decompressedLen + 1U);
                ::memcpy(str, decompressed, decompressedLen);
                str[decompressedLen] = 0; // null termination

                // randomize filename
                std::ostringstream s;
                std::random_device rd;
                std::mt19937 mt(rd());
                std::uniform_int_distribution<uint32_t> dist(0x00U, 0xFFFFFFFFU);
                s << "/tmp/rid_acl.dat." << dist(mt);

                std::string filename = s.str();
                std::ofstream file(filename, std::ofstream::out);
                if (file.fail()) {
                    LogError(LOG_NET, "Cannot open the radio ID lookup file - %s", filename.c_str());
                    m_ridPkt.clear();
                    delete[] decompressed;
                    break;
                }

                file << str;
                file.close();

                m_ridLookup->stop(true);
                m_ridLookup->filename(filename);
                m_ridLookup->reload();

                // flag this peer as replica enabled
                m_peerReplica = true;

                // cleanup temporary file
                ::remove(filename.c_str());
                m_ridPkt.clear();
                delete[] decompressed;
            }
        }
        break;

        default:
            break;
        }
    }
    break;

    default:
        Utils::dump("Unknown opcode from the master", data, length);
        break;
    }
}

/* Writes configuration to the network. */

bool PeerNetwork::writeConfig()
{
    if (m_loginStreamId == 0U) {
        LogWarning(LOG_NET, "BUGBUG: tried to write network authorisation with no stream ID?");
        return false;
    }

    const char* software = __NETVER__;

    json::object config = json::object();

    // identity and frequency
    config["identity"].set<std::string>(m_metadata->identity);                      // Identity
    config["rxFrequency"].set<uint32_t>(m_metadata->rxFrequency);                   // Rx Frequency
    config["txFrequency"].set<uint32_t>(m_metadata->txFrequency);                   // Tx Frequency

    // system info
    json::object sysInfo = json::object();
    sysInfo["latitude"].set<float>(m_metadata->latitude);                           // Latitude
    sysInfo["longitude"].set<float>(m_metadata->longitude);                         // Longitude

    sysInfo["height"].set<int>(m_metadata->height);                                 // Height
    sysInfo["location"].set<std::string>(m_metadata->location);                     // Location
    config["info"].set<json::object>(sysInfo);

    // channel data
    json::object channel = json::object();
    channel["txPower"].set<uint32_t>(m_metadata->power);                            // Tx Power
    channel["txOffsetMhz"].set<float>(m_metadata->txOffsetMhz);                     // Tx Offset (Mhz)
    channel["chBandwidthKhz"].set<float>(m_metadata->chBandwidthKhz);               // Ch. Bandwidth (khz)
    channel["channelId"].set<uint8_t>(m_metadata->channelId);                       // Channel ID
    channel["channelNo"].set<uint32_t>(m_metadata->channelNo);                      // Channel No
    config["channel"].set<json::object>(channel);

    // RCON
    json::object rcon = json::object();
    rcon["password"].set<std::string>(m_metadata->restApiPassword);                 // REST API Password
    rcon["port"].set<uint16_t>(m_metadata->restApiPort);                            // REST API Port
    config["rcon"].set<json::object>(rcon);

    uint32_t peerClass = PEER_CONN_CLASS::PEER_CONN_CLASS_SYSVIEW;
    config["peerClass"].set<uint32_t>(peerClass);                                   // Peer Connection Class
    config["masterPeerId"].set<uint32_t>(m_peerId);                                 // Master Peer ID

    bool convPeer = true;
    config["conventionalPeer"].set<bool>(convPeer);                                 // Conventional Peer Marker

    /*
    ** bryanb: these are all deprecated -- they remain here for backwards compatibility with older master versions, 
    **  but they are no longer used by the master and have no effect on R05A06 systems, and may be removed in a future release
    ** {
    */
    bool external = true;
    config["externalPeer"].set<bool>(external);                                     // External Peer Marker
    bool sysView = true;
    config["sysView"].set<bool>(sysView);                                           // SysView Peer Marker
    /*
    ** }
    */

    config["software"].set<std::string>(std::string(software));                     // Software ID

    json::value v = json::value(config);
    std::string json = v.serialize();

    DECLARE_CHAR_ARRAY(buffer, json.length() + 9U);

    ::memcpy(buffer + 0U, TAG_REPEATER_CONFIG, 4U);
    ::snprintf(buffer + 8U, json.length() + 1U, "%s", json.c_str());

    if (m_debug) {
        Utils::dump(1U, "PeerNetwork::writeConfig(), Message, Configuration", (uint8_t*)buffer, json.length() + 8U);
    }

    return writeMaster({ NET_FUNC::RPTC, NET_SUBFUNC::NOP }, (uint8_t*)buffer, json.length() + 8U, RTP_END_OF_CALL_SEQ, m_loginStreamId);
}
