////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// THIS IS AUTO-GENERATED CODE.  PLEASE DO NOT EDIT (File bug reports against tools).
///
/// Auto-generated by: Parameter Parser V5.5.1 (2411131018)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  parametereztunecombase.cpp
/// @brief Auto-generated Chromatix parameter file
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "parametereztunecombase.h"
#include "parameterutils.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ParameterEzTuneComBase
/// @brief Constructor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParameterEzTuneComBase::ParameterEzTuneComBase(IParameterLoggingAPI* logAPI)
{
    m_logAPI      = logAPI;
    m_serverID    = (UINT64)logAPI;
    m_exitData    = false;
    m_dataTimeout = RECV_TIMEOUT_SEC;
    m_readThread  = nullptr;
    m_writeThread = nullptr;
    m_recvBuffer  = PARAMETER_NEW BYTE[MAX_PACKET_LEN];
    m_recvPos     = 0;

    if (m_logAPI == nullptr)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterEzTuneComBase constructed with a nullptr Set Manager object\n");
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ~ParameterEzTuneComBase
/// @brief Destructor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParameterEzTuneComBase::~ParameterEzTuneComBase()
{
    PARAMETER_DELETE m_recvBuffer;
    m_recvBuffer = nullptr;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Start
/// @brief Starts the Ez Tune server
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneComBase::Start()
{
    if (m_logAPI != nullptr)
    {
        PARAMETER_LOG_DEBUG(m_logAPI, "Starting EzTune server: %s\n", m_logAPI->GetLoggingIdentifier());

        m_exitData = false;
        StartCom();

        m_readThread  = new std::thread(&ReadFunction, this);
        m_writeThread = new std::thread(&WriteFunction, this);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// StopEzSpectraTune
/// @brief Stops the Ez Tune server
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterEzTuneComBase::Stop()
{
    PARAMETER_LOG_DEBUG(m_logAPI, "Stopping TCP/IP EzTune server: %s\n", m_logAPI->GetLoggingIdentifier());

    m_exitData = true;
    m_readEvent.Set();
    m_writeEvent.Set();
    m_exitEvent.Set();

    if (m_readThread != nullptr && m_readThread->joinable())
    {
        m_readThread->join();
    }

    if (m_writeThread != nullptr && m_writeThread->joinable())
    {
        m_writeThread->join();
    }

    PARAMETER_DELETE m_readThread;
    PARAMETER_DELETE m_writeThread;
    m_readThread  = nullptr;
    m_writeThread = nullptr;

    StopCom();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Read
///
/// @brief Read a com packet.  Blocking function.  Will return when a full packet is received or after Stop is called.
/// @return The next Packet in the read queue, or nullptr if no packet is available before the timeout expires,
///         or Stop is called.  The returned Packet* must be deleted by the caller
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParameterEzTuneComBase::Packet* ParameterEzTuneComBase::Read(INT32 timeout)
{
    Packet* result = nullptr;

    if (m_readQueue.size() == 0)
    {
        m_readEvent.Wait(timeout * 1000);
    }

    // Lock to prevent the queue from changing while it is being read
    m_readMutex.lock();
    if (m_readQueue.size() > 0)
    {
        result = m_readQueue.front();
        m_readQueue.pop();
    }

    if (m_readQueue.size() == 0)
    {
        m_readEvent.Reset();
    }
    m_readMutex.unlock();
    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Read
///
/// @brief Reads com packet into the provided buffer
/// @param buffer - Buffer to read into
/// @param len    - Length of buffer to read into
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PARAMETER_INLINE UINT32 ParameterEzTuneComBase::Read(BYTE* buffer, UINT32 len, INT32 timeout)
{
    UINT32 result = 0U;

    if (buffer != nullptr)
    {
        Packet* packet = Read(timeout);

        if (packet != nullptr)
        {
            if (packet->Buffer != nullptr && packet->Length <= len)
            {
                result = packet->Length;
                PARAMETER_MEMCPY(buffer, len, packet->Buffer, packet->Length);
            }

            PARAMETER_DELETE packet;
        }
    }
    return result;
}

INT32 ParameterEzTuneComBase::ReceiveEzTuneRequest(
    BYTE*  packet,
    UINT32 packetSize,
    BYTE   commandMax,
    INT32  timeout,
    bool&  exit)
{
    INT32 result = 0;
    time_t startTime;
    time_t currTime;
    time(&startTime);

    do
    {
        if (m_recvPos >= ID_POS)
        {
            UINT16 cmd    = 0;
            UINT32 cmdPos = CMD_POS;
            UINT16 tag    = 0;
            UINT32 tagPos = TAG_POS;
            ParameterUtils::Read(&cmd, m_recvBuffer, m_recvPos, &cmdPos, 1, m_logAPI);
            ParameterUtils::Read(&tag, m_recvBuffer, m_recvPos, &tagPos, 1, m_logAPI);
            if (tag != TAG)
            {
                // Start tag is missing
                PARAMETER_LOG_ERROR(m_logAPI, "EzTune request missing start tag, Expected: %u, Received: %u: 0x%02x%02x%02x%02x%02x\n",
                    TAG, tag, m_recvBuffer[0], m_recvBuffer[1], m_recvBuffer[2], m_recvBuffer[3], m_recvBuffer[4], commandMax);
                m_recvPos = 0;
            }
            else if (cmd >= commandMax)
            {
                // Invalid packet received.
                PARAMETER_LOG_ERROR(m_logAPI, "EzTune request ID %u exceeds Max %u\n", cmd, commandMax);
                result = 0;
            }
            else
            {
                UINT16 length = 0;
                UINT32 pos    = LEN_POS;
                ParameterUtils::Read(&length, m_recvBuffer, MAX_PACKET_LEN, &pos, 1, m_logAPI);

                if (length == 0)
                {
                    PARAMETER_LOG_ERROR(m_logAPI, "EzTune request length is zero: 0x%02x%02x%02x%02x%02x\n",
                        m_recvBuffer[0], m_recvBuffer[1], m_recvBuffer[2], m_recvBuffer[3], m_recvBuffer[4], commandMax);
                    result = 0;
                }
                else if (length <= m_recvPos)
                {
                    PARAMETER_MEMCPY(packet, packetSize, m_recvBuffer, length);
                    result = length;

                    if (m_recvPos > length)
                    {
                        // Received the start of the next packet, move it to the front of the buffer
                        PARAMETER_MEMMOVE(m_recvBuffer, MAX_PACKET_LEN, &m_recvBuffer[length], m_recvPos - length);
                    }
                    m_recvPos = m_recvPos - length;

                    if (cmd >= CMD_MAX)
                    {
                        UINT16 id = 0;
                        pos = ID_POS;
                        ParameterUtils::Read(&id, m_recvBuffer, MAX_PACKET_LEN, &pos, 1, m_logAPI);

                        constexpr INT32 MAX_PRINT = 128;
                        CHAR printPayload[MAX_PRINT];
                        SIZE_T len = PARAMETER_STRLEN((CHAR*)&packet[PAY_POS]);
                        if (len >= MAX_PRINT)
                        {
                            len = MAX_PRINT - 1;
                        }
                        PARAMETER_MEMCPY(printPayload, len, &packet[PAY_POS], len);
                        printPayload[len] = '\0';

                        PARAMETER_LOG_DEBUG(m_logAPI, "EzTune recv %u bytes, Command %d, ID: %u, Payload: %s\n", length, cmd, id, printPayload);
                        if (packet[length - 1] != 0)
                        {
                            PARAMETER_LOG_ERROR(m_logAPI, "EzTune packet missing terminating nul character\n");
                        }
                    }
                }
                else
                {
                    // Incomplete packets are to be expected, enable for debugging
                    // PARAMETER_LOG_DEBUG(m_logAPI, "EzTune insufficent data: Expected: %u, Received: %u\n", length, m_recvPos);
                }
            }
        }

        time(&currTime);
        if (result == 0 && !exit && (timeout < 0 || difftime(currTime, startTime) < timeout))
        {
            INT32 recv = ReadData(&m_recvBuffer[m_recvPos], MAX_PACKET_LEN - m_recvPos, timeout, exit);
            if (recv >= 0)
            {
                m_recvPos += recv;
            }
        }

    } while (result == 0 && !exit && (timeout < 0 || difftime(currTime, startTime)  < timeout));

    return result;
}

void ParameterEzTuneComBase::ReadFunction(void* worker)
{
    if (worker != nullptr)
    {
        ((ParameterEzTuneComBase*)worker)->ReadWorker();
    }
}

void ParameterEzTuneComBase::WriteFunction(void* worker)
{
    if (worker != nullptr)
    {
        ((ParameterEzTuneComBase*)worker)->WriteWorker();
    }
}

void ParameterEzTuneComBase::ReadWorker()
{
    INT32 reqLen = 0;
    BYTE  request[MAX_PACKET_LEN];

    do
    {
        if (!m_exitData)
        {
            reqLen = ReceiveEzTuneRequest(request, MAX_PACKET_LEN, CMD_MAX, m_dataTimeout, m_exitData);
        }

        if (!m_exitData && reqLen > 0)
        {
            // Lock to prevent the queue from changing while it is being read
            m_readMutex.lock();
            m_readQueue.push(PARAMETER_NEW Packet(request, reqLen));
            m_readEvent.Set();
            m_readMutex.unlock();
        }
    } while (!m_exitData);
}

void ParameterEzTuneComBase::WriteWorker()
{
    do
    {
        if (!m_exitData && m_writeQueue.size() == 0)
        {
            m_writeEvent.Wait(m_dataTimeout * 1000);
        }

        // Lock to prevent the queue from changing while it is being read
        m_writeMutex.lock();
        if (!m_exitData && m_writeQueue.size() > 0)
        {
            Packet* packet = m_writeQueue.front();
            m_writeQueue.pop();
            if (packet->Length > CMD_POS)
            {
                UINT32 sent = WriteData(packet->Buffer, packet->Length);

                PARAMETER_LOG_DEBUG(m_logAPI, "EzTune write %d bytes, Command: %d\n",
                    sent, packet->Buffer[CMD_POS]);
            }
            else
            {
                PARAMETER_LOG_DEBUG(m_logAPI, "EzTune write insufficient buffer size %d\n", packet->Length);
            }
            PARAMETER_DELETE packet;
        }

        if (m_writeQueue.size() == 0)
        {
            m_writeEvent.Reset();
        }
        m_writeMutex.unlock();

    } while (!m_exitData);
}
