/**
 * @file UDPStreamerClient.cpp
 * @brief Source file for class UDPStreamerClient
 * @date 24/06/2026
 * @author Martino Ferrari
 *
 * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
 * the Development of Fusion Energy ('Fusion for Energy').
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved
 * by the European Commission - subsequent versions of the EUPL (the "Licence")
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
 *
 * @warning Unless required by applicable law or agreed to in writing,
 * software distributed under the Licence is distributed on an "AS IS"
 * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the Licence permissions and limitations under the Licence.
 *
 * @details This source file contains the definition of all the methods for
 * the class UDPStreamerClient (public, protected, and private).
 */

#define DLL_API

/*---------------------------------------------------------------------------*/
/*                         Standard header includes                          */
/*---------------------------------------------------------------------------*/
#include <string.h>

/*---------------------------------------------------------------------------*/
/*                         Project header includes                           */
/*---------------------------------------------------------------------------*/
#include "AdvancedErrorManagement.h"
#include "ConfigurationDatabase.h"
#include "GlobalObjectsDatabase.h"
#include "MemoryOperationsHelper.h"
#include "UDPSProtocol.h"
#include "UDPStreamerClient.h"

/*---------------------------------------------------------------------------*/
/*                           Static definitions                              */
/*---------------------------------------------------------------------------*/

namespace MARTe {

/** Default server address when none is specified. */
static const char8 *const UDPS_CLIENT_DEFAULT_ADDR = "127.0.0.1";

/** Default port used when none is specified. */
static const uint16 UDPS_CLIENT_DEFAULT_PORT = 44500u;

/** Default data-port offset: dataPort = port + this when DataPort is absent. */
static const uint16 UDPS_CLIENT_DEFAULT_DP_OFFSET = 1u;

/** Default max payload per UDP datagram (bytes). */
static const uint32 UDPS_CLIENT_DEFAULT_MAX_PAYLOAD = 1400u;

/** Bytes prepended to each DATA payload for the HRT packet timestamp. */
static const uint32 UDPS_CLIENT_TIMESTAMP_BYTES = 8u;

/*---------------------------------------------------------------------------*/
/*                           Decode helpers                                  */
/*---------------------------------------------------------------------------*/

/**
 * @brief Reads one quantised wire element (1 or 2 bytes) as a float64,
 * mirroring UDPSourceSession::DecodeRawValue for the quantised type codes.
 */
static float64 DecodeQuantRaw(const uint8 *ptr, uint8 quantType) {
    float64 raw = 0.0;
    switch (quantType) {
        case UDPS_QUANT_UINT8: {
            uint8 v = 0u; (void) memcpy(&v, ptr, 1u); raw = static_cast<float64>(v);
            break;
        }
        case UDPS_QUANT_INT8: {
            int8 v = 0; (void) memcpy(&v, ptr, 1u); raw = static_cast<float64>(v);
            break;
        }
        case UDPS_QUANT_UINT16: {
            uint16 v = 0u; (void) memcpy(&v, ptr, 2u); raw = static_cast<float64>(v);
            break;
        }
        case UDPS_QUANT_INT16: {
            int16 v = 0; (void) memcpy(&v, ptr, 2u); raw = static_cast<float64>(v);
            break;
        }
        default:
            break;
    }
    return raw;
}

/**
 * @brief Dequantises a raw value, mirroring UDPSourceSession::DequantizeValue.
 */
static float64 DequantValue(float64 raw, uint8 quantType,
                            float64 rangeMin, float64 rangeMax) {
    const float64 range = rangeMax - rangeMin;
    float64 val;
    switch (quantType) {
        case UDPS_QUANT_UINT8:
            val = rangeMin + (raw / 255.0) * range;
            break;
        case UDPS_QUANT_INT8:
            val = rangeMin + ((raw + 127.0) / 254.0) * range;
            break;
        case UDPS_QUANT_UINT16:
            val = rangeMin + (raw / 65535.0) * range;
            break;
        case UDPS_QUANT_INT16:
            val = rangeMin + ((raw + 32767.0) / 65534.0) * range;
            break;
        default:
            val = raw;
            break;
    }
    return val;
}

/*---------------------------------------------------------------------------*/
/*                           Method definitions                              */
/*---------------------------------------------------------------------------*/

UDPStreamerClient::UDPStreamerClient() :
        MemoryDataSourceI(),
        UDPSClientListener(),
        client() {
    serverAddress   = UDPS_CLIENT_DEFAULT_ADDR;
    port            = UDPS_CLIENT_DEFAULT_PORT;
    maxPayloadSize  = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD;
    cpuMask         = 0xFFFFFFFFu;
    stackSize       = THREADS_DEFAULT_STACKSIZE;
    dataPort        = UDPS_CLIENT_DEFAULT_PORT + UDPS_CLIENT_DEFAULT_DP_OFFSET;
    useMulticast    = false;
    numSigs         = 0u;
    signalInfos     = NULL_PTR(UDPStreamerClientSignal *);
    totalSrcBytes   = 0u;
    configValidated = false;
    publishMode     = UDPS_PUBLISH_STRICT;
    receiverUp      = false;
    readyBuffer     = NULL_PTR(uint8 *);
    scratchBuffer   = NULL_PTR(uint8 *);
    newDataReady    = false;

    if (!dataSem.Create()) {
        REPORT_ERROR(ErrorManagement::FatalError, "Could not create EventSem.");
    }
    bufMutex.Create(false);
}

UDPStreamerClient::~UDPStreamerClient() {
    (void) dataSem.Post();

    if (receiverUp) {
        (void) client.Stop();
        receiverUp = false;
    }

    HeapI *heap = GlobalObjectsDatabase::Instance()->GetStandardHeap();
    if (signalInfos != NULL_PTR(UDPStreamerClientSignal *)) {
        delete[] signalInfos;
        signalInfos = NULL_PTR(UDPStreamerClientSignal *);
    }
    if (readyBuffer != NULL_PTR(uint8 *)) {
        heap->Free(reinterpret_cast<void *&>(readyBuffer));
    }
    if (scratchBuffer != NULL_PTR(uint8 *)) {
        heap->Free(reinterpret_cast<void *&>(scratchBuffer));
    }

    (void) dataSem.Close();
}

bool UDPStreamerClient::Initialise(StructuredDataI &data) {
    bool ok = MemoryDataSourceI::Initialise(data);

    if (ok) {
        StreamString addr = "";
        if (!data.Read("ServerAddress", addr) || (addr.Size() == 0u)) {
            addr = UDPS_CLIENT_DEFAULT_ADDR;
            REPORT_ERROR(ErrorManagement::Information,
                         "ServerAddress not specified; using default %s.",
                         UDPS_CLIENT_DEFAULT_ADDR);
        }
        serverAddress = addr;
    }

    if (ok) {
        if (!data.Read("Port", port)) {
            port = UDPS_CLIENT_DEFAULT_PORT;
            REPORT_ERROR(ErrorManagement::Information,
                         "Port not specified; using default %u.",
                         static_cast<uint32>(port));
        }
    }

    if (ok) {
        if (!data.Read("MaxPayloadSize", maxPayloadSize)) {
            maxPayloadSize = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD;
        }
    }

    if (ok) {
        if (!data.Read("CPUMask", cpuMask)) {
            cpuMask = 0xFFFFFFFFu;
        }
    }

    if (ok) {
        if (!data.Read("StackSize", stackSize)) {
            stackSize = THREADS_DEFAULT_STACKSIZE;
        }
    }

    if (ok) {
        StreamString mcastStr = "";
        (void) data.Read("MulticastGroup", mcastStr);
        if (mcastStr.Size() > 0u) {
            multicastGroup = mcastStr;
            useMulticast   = true;
            uint16 dp = 0u;
            if (!data.Read("DataPort", dp)) {
                dp = port + UDPS_CLIENT_DEFAULT_DP_OFFSET;
            }
            dataPort = dp;
            REPORT_ERROR(ErrorManagement::Information,
                         "Multicast mode: group=%s, server=%s, controlPort=%u, dataPort=%u.",
                         multicastGroup.Buffer(), serverAddress.Buffer(),
                         static_cast<uint32>(port), static_cast<uint32>(dataPort));
        }
        else {
            useMulticast = false;
        }
    }

    /* Forward the transport parameters to the shared UDPSClient receiver,
     * translating the DataSource parameter names to the UDPSClient keys. */
    if (ok) {
        ConfigurationDatabase cdb;
        ok = cdb.Write("ServerAddr", serverAddress);
        if (ok) { ok = cdb.Write("Port", static_cast<uint32>(port)); }
        if (ok && useMulticast) {
            ok = cdb.Write("MulticastGroup", multicastGroup);
            if (ok) { ok = cdb.Write("DataPort", static_cast<uint32>(dataPort)); }
        }
        if (ok) { ok = cdb.Write("MaxPayloadSize", maxPayloadSize); }
        if (ok) { ok = cdb.Write("CPUMask", cpuMask); }
        if (ok) { ok = cdb.Write("StackSize", stackSize); }
        if (ok) { ok = cdb.MoveToRoot(); }
        if (ok) { ok = client.Initialise(cdb); }
        if (!ok) {
            REPORT_ERROR(ErrorManagement::ParametersError,
                         "Could not configure the UDPSClient receiver.");
        }
        client.SetListener(this);
    }

    return ok;
}

bool UDPStreamerClient::SetConfiguredDatabase(StructuredDataI &data) {
    bool ok = MemoryDataSourceI::SetConfiguredDatabase(data);
    if (!ok) {
        return false;
    }

    numSigs = GetNumberOfSignals();
    if (numSigs == 0u) {
        REPORT_ERROR(ErrorManagement::ParametersError,
                     "At least one signal must be defined.");
        return false;
    }

    signalInfos = new UDPStreamerClientSignal[numSigs];

    totalSrcBytes = 0u;
    for (uint32 i = 0u; (i < numSigs) && ok; i++) {
        StreamString sigName;
        ok = GetSignalName(i, sigName);
        if (!ok) {
            REPORT_ERROR(ErrorManagement::FatalError,
                         "Could not get name for signal %u.", i);
            break;
        }

        uint32 nelems = 1u;
        (void) GetSignalNumberOfElements(i, nelems);
        if (nelems == 0u) { nelems = 1u; }

        uint32 bsz = 0u;
        (void) GetSignalByteSize(i, bsz);

        signalInfos[i].name          = sigName;
        signalInfos[i].type          = GetSignalType(i);
        signalInfos[i].quantType     = UDPS_QUANT_NONE;
        signalInfos[i].numElements   = nelems;
        signalInfos[i].rangeMin      = 0.0;
        signalInfos[i].rangeMax      = 1.0;
        signalInfos[i].srcByteSize   = bsz;
        signalInfos[i].wireElemBytes = (nelems > 0u) ? (bsz / nelems) : bsz;
        signalInfos[i].bufferOffset  = totalSrcBytes;
        totalSrcBytes += bsz;
    }

    return ok;
}

bool UDPStreamerClient::AllocateMemory() {
    bool ok = MemoryDataSourceI::AllocateMemory();
    if (!ok) {
        return false;
    }

    if (totalSrcBytes == 0u) {
        totalSrcBytes = stateMemorySize;
    }

    HeapI *heap = GlobalObjectsDatabase::Instance()->GetStandardHeap();

    readyBuffer = reinterpret_cast<uint8 *>(heap->Malloc(totalSrcBytes));
    if (readyBuffer == NULL_PTR(uint8 *)) {
        REPORT_ERROR(ErrorManagement::FatalError, "Could not allocate readyBuffer.");
        return false;
    }
    (void) MemoryOperationsHelper::Set(readyBuffer, 0, totalSrcBytes);

    scratchBuffer = reinterpret_cast<uint8 *>(heap->Malloc(totalSrcBytes));
    if (scratchBuffer == NULL_PTR(uint8 *)) {
        REPORT_ERROR(ErrorManagement::FatalError, "Could not allocate scratchBuffer.");
        return false;
    }
    (void) MemoryOperationsHelper::Set(scratchBuffer, 0, totalSrcBytes);

    return true;
}

const char8 *UDPStreamerClient::GetBrokerName(StructuredDataI &data,
                                              const SignalDirection direction) {
    const char8 *brokerName = "";
    if (direction == InputSignals) {
        brokerName = "MemoryMapSynchronisedInputBroker";
    }
    return brokerName;
}

bool UDPStreamerClient::PrepareNextState(const char8 *const currentStateName,
                                         const char8 *const nextStateName) {
    bool ok = true;
    if (!receiverUp) {
        ok = client.Start();
        if (ok) {
            receiverUp = true;
        }
        else {
            REPORT_ERROR(ErrorManagement::FatalError,
                         "Could not start the UDPSClient receiver.");
        }
    }
    return ok;
}

bool UDPStreamerClient::Synchronise() {
    ErrorManagement::ErrorType waitErr = dataSem.ResetWait(TimeoutType(10u));
    if (waitErr == ErrorManagement::NoError) {
        bufMutex.FastLock(TTInfiniteWait);
        (void) MemoryOperationsHelper::Copy(memory, readyBuffer, totalSrcBytes);
        newDataReady = false;
        bufMutex.FastUnLock();
    }
    return true;
}

/*---------------------------------------------------------------------------*/
/*                       UDPSClientListener callbacks                        */
/*---------------------------------------------------------------------------*/

void UDPStreamerClient::OnUDPSConfig(const uint8 *payload, uint32 payloadSize) {
    if (payloadSize < 4u) {
        REPORT_ERROR(ErrorManagement::ParametersError,
                     "CONFIG payload too small (%u bytes).", payloadSize);
        return;
    }

    uint32 serverNumSigs = 0u;
    (void) memcpy(&serverNumSigs, payload, 4u);

    if (serverNumSigs != numSigs) {
        REPORT_ERROR(ErrorManagement::ParametersError,
                     "CONFIG signal count mismatch: server=%u, client=%u.",
                     serverNumSigs, numSigs);
        return;
    }

    const uint32 needed = 4u + (numSigs * UDPS_SIGNAL_DESC_SIZE);
    if (payloadSize < needed) {
        REPORT_ERROR(ErrorManagement::ParametersError,
                     "CONFIG payload truncated (%u < %u bytes).", payloadSize, needed);
        return;
    }

    bool ok = true;
    for (uint32 i = 0u; (i < numSigs) && ok; i++) {
        UDPSSignalDescriptor desc;
        (void) memcpy(&desc, payload + 4u + (i * UDPS_SIGNAL_DESC_SIZE),
                      UDPS_SIGNAL_DESC_SIZE);

        char8 nameBuf[UDPS_MAX_SIGNAL_NAME + 1u];
        (void) memcpy(nameBuf, desc.name, UDPS_MAX_SIGNAL_NAME);
        nameBuf[UDPS_MAX_SIGNAL_NAME] = '\0';
        StreamString serverName = nameBuf;

        if (signalInfos[i].name != serverName) {
            REPORT_ERROR(ErrorManagement::ParametersError,
                         "CONFIG signal %u name mismatch: server=%s, client=%s.",
                         i, serverName.Buffer(), signalInfos[i].name.Buffer());
            ok = false;
            break;
        }

        uint32 serverElems = desc.numRows * desc.numCols;
        if (serverElems == 0u) { serverElems = 1u; }
        if (serverElems != signalInfos[i].numElements) {
            REPORT_ERROR(ErrorManagement::ParametersError,
                         "CONFIG signal %u element count mismatch: server=%u, client=%u.",
                         i, serverElems, signalInfos[i].numElements);
            ok = false;
            break;
        }

        signalInfos[i].quantType = desc.quantType;
        signalInfos[i].rangeMin  = desc.rangeMin;
        signalInfos[i].rangeMax  = desc.rangeMax;
        if (desc.quantType != UDPS_QUANT_NONE) {
            uint32 q = 0u;
            if ((desc.quantType == UDPS_QUANT_UINT8) ||
                (desc.quantType == UDPS_QUANT_INT8)) {
                q = 1u;
            }
            else if ((desc.quantType == UDPS_QUANT_UINT16) ||
                     (desc.quantType == UDPS_QUANT_INT16)) {
                q = 2u;
            }
            signalInfos[i].wireElemBytes = q;
        }
    }

    if (ok) {
        const uint32 pmOffset = needed;
        publishMode = (payloadSize > pmOffset) ? payload[pmOffset] : UDPS_PUBLISH_STRICT;

        bufMutex.FastLock(TTInfiniteWait);
        configValidated = true;
        bufMutex.FastUnLock();

        REPORT_ERROR(ErrorManagement::Information,
                     "CONFIG validated: %u signals, publishMode=%u, totalSrcBytes=%u.",
                     numSigs, static_cast<uint32>(publishMode), totalSrcBytes);
    }
}

void UDPStreamerClient::OnUDPSData(const uint8 *payload, uint32 payloadSize) {
    if (!configValidated) {
        return;
    }
    if (payloadSize < UDPS_CLIENT_TIMESTAMP_BYTES) {
        return;
    }

    (void) MemoryOperationsHelper::Set(scratchBuffer, 0, totalSrcBytes);
    DecodeSnapshot(payload, payloadSize, scratchBuffer);

    bufMutex.FastLock(TTInfiniteWait);
    (void) MemoryOperationsHelper::Copy(readyBuffer, scratchBuffer, totalSrcBytes);
    newDataReady = true;
    bufMutex.FastUnLock();

    (void) dataSem.Post();
}

void UDPStreamerClient::OnUDPSConnected() {
    REPORT_ERROR(ErrorManagement::Information,
                 "UDPStreamerClient connected to %s:%u.",
                 serverAddress.Buffer(), static_cast<uint32>(port));
}

void UDPStreamerClient::OnUDPSDisconnected() {
    bufMutex.FastLock(TTInfiniteWait);
    configValidated = false;
    bufMutex.FastUnLock();
    REPORT_ERROR(ErrorManagement::Information, "UDPStreamerClient disconnected.");
}

/*---------------------------------------------------------------------------*/
/*                              Decode logic                                 */
/*---------------------------------------------------------------------------*/

void UDPStreamerClient::DecodeSnapshot(const uint8 *payload, uint32 size,
                                       uint8 *dst) {
    uint32 offset     = UDPS_CLIENT_TIMESTAMP_BYTES;
    uint32 numSamples = 1u;

    if (publishMode == UDPS_PUBLISH_ACCUMULATE) {
        if ((offset + 4u) > size) { return; }
        (void) memcpy(&numSamples, payload + offset, 4u);
        offset += 4u;
        if (numSamples == 0u) { numSamples = 1u; }
    }

    uint32 off = offset;
    for (uint32 s = 0u; s < numSigs; s++) {
        const UDPStreamerClientSignal &info = signalInfos[s];
        const uint32 wireElemBytes = info.wireElemBytes;
        if (wireElemBytes == 0u) { return; }

        const uint32 ne = info.numElements;
        const bool accScalar = (publishMode == UDPS_PUBLISH_ACCUMULATE) && (ne == 1u);
        const uint32 elemsToRead = accScalar ? numSamples : ne;

        /* HI-1: 64-bit bounds check to prevent uint32 multiply overflow */
        uint64 bytesNeeded = static_cast<uint64>(off) +
                             static_cast<uint64>(elemsToRead) *
                             static_cast<uint64>(wireElemBytes);
        if (bytesNeeded > static_cast<uint64>(size)) { return; }

        uint8 *d = dst + info.bufferOffset;

        if (info.quantType == UDPS_QUANT_NONE) {
            if (accScalar) {
                /* Publish the most recent accumulated sample. */
                const uint8 *srcLast = payload + off +
                                       ((numSamples - 1u) * wireElemBytes);
                (void) memcpy(d, srcLast, wireElemBytes);
            }
            else {
                (void) memcpy(d, payload + off, ne * wireElemBytes);
            }
        }
        else {
            const bool isF32 = (info.type == Float32Bit);
            const uint32 startElem = accScalar ? (numSamples - 1u) : 0u;
            const uint32 count     = accScalar ? 1u : ne;
            for (uint32 e = 0u; e < count; e++) {
                const uint8 *ptr = payload + off +
                                   ((startElem + e) * wireElemBytes);
                const float64 raw = DecodeQuantRaw(ptr, info.quantType);
                const float64 val = DequantValue(raw, info.quantType,
                                                 info.rangeMin, info.rangeMax);
                if (isF32) {
                    float32 f32 = static_cast<float32>(val);
                    (void) memcpy(d, &f32, 4u);
                    d += 4u;
                }
                else {
                    float64 f64 = val;
                    (void) memcpy(d, &f64, 8u);
                    d += 8u;
                }
            }
        }

        off += elemsToRead * wireElemBytes;
    }
}

CLASS_REGISTER(UDPStreamerClient, "1.0")

} /* namespace MARTe */
