/*
 * Copyright (c) 2014, Freescale Semiconductor, Inc.
 * Copyright 2016-2021 NXP
 * Copyright 2021 ACRIOS Systems s.r.o.
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "erpc_client_manager.h"


using namespace erpc;

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

#if ERPC_NESTED_CALLS_DETECTION
extern bool nestingDetection;
#ifndef _WIN32
#pragma weak nestingDetection
bool nestingDetection = false;
#endif
#endif

RequestContext ClientManager::createRequest(bool isOneway)
{
    // Create codec to read and write the request.
    Codec *codec = createBufferAndCodec();

    return RequestContext(++m_sequence, codec, isOneway);
}

void ClientManager::performRequest(RequestContext &request)
{
    bool performRequest;

    // Check the codec status
    performRequest = request.getCodec()->isStatusOk();

#if ERPC_NESTED_CALLS
    if (performRequest)
    {
        erpc_assert((m_serverThreadId != NULL) && ("server thread id was not set" != NULL));
        if (Thread::getCurrentThreadId() == m_serverThreadId)
        {
            performNestedClientRequest(request);
            performRequest = false;
        }
    }
#endif
    if (performRequest)
    {

        performClientRequest(request);

    }
}

void ClientManager::performClientRequest(RequestContext &request)
{
    erpc_status_t err;

#if ERPC_NESTED_CALLS_DETECTION
    if (!request.isOneway() && nestingDetection)
    {
        request.getCodec()->updateStatus(kErpcStatus_NestedCallFailure);
    }
#endif

#if ERPC_MESSAGE_LOGGING
    if (request.getCodec()->isStatusOk() == true)
    {
        err = logMessage(request.getCodec()->getBuffer());
        request.getCodec()->updateStatus(err);
    }
#endif

    // Send invocation request to server.
    if (request.getCodec()->isStatusOk() == true)
    {

        err = m_transport->send(&request.getCodec()->getBufferRef());
        request.getCodec()->updateStatus(err);
    }

    // If the request is oneway, then there is nothing more to do.
    if (!request.isOneway())
    {
        if (request.getCodec()->isStatusOk() == true)
        {
            // Receive reply.
            err = m_transport->receive(&request.getCodec()->getBufferRef());
            request.getCodec()->updateStatus(err);
        }

#if ERPC_MESSAGE_LOGGING
        if (request.getCodec()->isStatusOk() == true)
        {
            err = logMessage(request.getCodec()->getBuffer());
            request.getCodec()->updateStatus(err);
        }
#endif

        // Check the reply.
        if (request.getCodec()->isStatusOk() == true)
        {
            verifyReply(request);
        }
    }
}

#if ERPC_NESTED_CALLS
void ClientManager::performNestedClientRequest(RequestContext &request)
{
    erpc_status_t err;

    erpc_assert((m_transport != NULL) && ("transport/arbitrator not set" != NULL));

#if ERPC_MESSAGE_LOGGING
    if (request.getCodec()->isStatusOk() == true)
    {
        err = logMessage(request.getCodec()->getBuffer());
        request.getCodec()->updateStatus(err);
    }
#endif

    // Send invocation request to server.
    if (request.getCodec()->isStatusOk() == true)
    {
        err = m_transport->send(&request.getCodec()->getBufferRef());
        request.getCodec()->updateStatus(err);
    }

    // If the request is oneway, then there is nothing more to do.
    if (!request.isOneway())
    {
        // Receive reply.
        if (request.getCodec()->isStatusOk() == true)
        {
            erpc_assert((m_server != NULL) && ("server for nesting calls was not set" != NULL));
            err = m_server->run(request);
            request.getCodec()->updateStatus(err);
        }

#if ERPC_MESSAGE_LOGGING
        if (request.getCodec()->isStatusOk() == true)
        {
            err = logMessage(request.getCodec()->getBuffer());
            request.getCodec()->updateStatus(err);
        }
#endif

        // Check the reply.
        if (request.getCodec()->isStatusOk() == true)
        {
            verifyReply(request);
        }
    }
}
#endif

void ClientManager::verifyReply(RequestContext &request)
{
    message_type_t msgType;
    uint32_t service;
    uint32_t requestNumber;
    uint32_t sequence;

    // Some transport layers change the request's message buffer pointer (for things like zero
    // copy support), so inCodec must be reset to work with correct buffer.
    request.getCodec()->reset(m_transport->reserveHeaderSize());

    // Extract the reply header.
    request.getCodec()->startReadMessage(msgType, service, requestNumber, sequence);

    if (request.getCodec()->isStatusOk() == true)
    {
        // Verify that this is a reply to the request we just sent.
        if ((msgType != message_type_t::kReplyMessage) || (sequence != request.getSequence()))
        {
            request.getCodec()->updateStatus(kErpcStatus_ExpectedReply);
        }
    }
}

Codec *ClientManager::createBufferAndCodec(void)
{
    Codec *codec = m_codecFactory->create();
    MessageBuffer message;
    uint8_t reservedMessageSpace = m_transport->reserveHeaderSize();

    if (codec != NULL)
    {
        message = m_messageFactory->create(reservedMessageSpace);
        if (NULL != message.get())
        {
            codec->setBuffer(message, reservedMessageSpace);
        }
        else
        {
            // Dispose of buffers and codecs.
            m_codecFactory->dispose(codec);
            codec = NULL;
        }
    }

    return codec;
}

void ClientManager::releaseRequest(RequestContext &request)
{
    if (request.getCodec() != NULL)
    {
        m_messageFactory->dispose(&request.getCodec()->getBufferRef());
        m_codecFactory->dispose(request.getCodec());
    }
}

void ClientManager::callErrorHandler(erpc_status_t err, uint32_t functionID)
{
    if (m_errorHandler != NULL)
    {
        m_errorHandler(err, functionID);
    }
}
