/* SPDX-License-Identifier: Apache-2.0 */
/******************************************************************************/
/**                                                                           */
/** RK0 - The Embedded Real-Time Kernel '0'                                   */
/** (C) 2026 Antonio Giacomelli <dev@kernel0.org>                             */
/**                                                                           */
/** VERSION: V0.60.0 */
/**                                                                           */
/** You may obtain a copy of the License at :                                 */
/** http://www.apache.org/licenses/LICENSE-2.0                                */
/**                                                                           */
/******************************************************************************/
/******************************************************************************/
/* COMPONENT: COUNTING SEMAPHORE                                              */
/******************************************************************************/

#define RK_SOURCE_CODE

#include <ksema.h>
#include <ktrace.h>
#if (RK_CONF_SEMAPHORE == ON)
/******************************************************************************/
/* COUNTING/BIN SEMAPHORES                                                    */
/******************************************************************************/
/******************************************************************************/
/* A semaphore has a maxValue. To create a binary semaphore set its max value */
/* to 1U. Note, when signalling a semaphore whose value reached its limit,    */
/* return code is not a FAULT (negative) but a positive value                 */
/* (RK_ERR_SEMA_FULL).                                                        */
/* Depending on the case it might or not mean an error in the synch logic.    */
/******************************************************************************/

#ifndef K_SEMA_IS_BINARY
#define K_SEMA_IS_BINARY(kobj) ((kobj)->maxValue == 1U)
#endif

RK_ERR kSemaphoreInit(RK_SEMAPHORE *const kobj, const UINT initValue,
                      const UINT maxValue)
{
    RK_CR_AREA
    RK_CR_ENTER

#if (RK_CONF_ERR_CHECK == ON)

    if (kobj == NULL)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NULL);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NULL);
    }
    if (kobj->init == RK_TRUE)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_DOUBLE_INIT);
        RK_CR_EXIT
        return (RK_ERR_OBJ_DOUBLE_INIT);
    }
    if ((maxValue == 0U) || (initValue > maxValue))
    {
        K_ERR_HANDLER(RK_FAULT_INVALID_PARAM);
        RK_CR_EXIT
        return (RK_ERR_INVALID_PARAM);
    }
    if (kTCBQInit(&(kobj->waitingQueue)) != RK_ERR_SUCCESS)
    {
        RK_CR_EXIT
        return (RK_ERR_ERROR);
    }
#endif

    kobj->init = RK_TRUE;
    kobj->objID = RK_SEMAPHORE_KOBJ_ID;
    kobj->objName[0] = '\0';
    kobj->maxValue = maxValue;
    kobj->value = initValue;
    kTraceRegisterObject(kobj, RK_SEMAPHORE_KOBJ_ID);
    RK_CR_EXIT
    return (RK_ERR_SUCCESS);
}

RK_ERR kSemaphorePend(RK_SEMAPHORE *const kobj, const RK_TICK timeout)
{
    RK_CR_AREA
    RK_CR_ENTER
#if (RK_CONF_ERR_CHECK == ON)

    if (kobj == NULL)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NULL);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NULL);
    }

    if (kobj->objID != RK_SEMAPHORE_KOBJ_ID)
    {
        K_ERR_HANDLER(RK_FAULT_INVALID_OBJ);
        RK_CR_EXIT
        return (RK_ERR_INVALID_OBJ);
    }

    if (kobj->init == RK_FALSE)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NOT_INIT);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NOT_INIT);
    }

    if (K_BLOCKING_ON_ISR(timeout))
    {
        RK_CR_EXIT
        K_ERR_HANDLER(RK_FAULT_INVALID_ISR_PRIMITIVE);
        return (RK_ERR_INVALID_ISR_PRIMITIVE);
    }

#endif

    if (kobj->value > 0U)
    {
        if (K_SEMA_IS_BINARY(kobj))
        {
            kobj->value = 0U;
        }
        else
        {
            kobj->value = kobj->value - 1U;
        }
        kTraceRecordObject(kobj, RK_TRACE_OP_PEND, RK_ERR_SUCCESS,
                           kobj->value);
        RK_CR_EXIT
        return (RK_ERR_SUCCESS);
    }
    else
    {

        if (timeout == RK_NO_WAIT)
        {
            kTraceRecordObject(kobj, RK_TRACE_OP_PEND, RK_ERR_SEMA_BLOCKED,
                               kobj->waitingQueue.size);
            RK_CR_EXIT
            return (RK_ERR_SEMA_BLOCKED);
        }
        if ((timeout != RK_WAIT_FOREVER) && (timeout > 0))
        {
            RK_TASK_TIMEOUT_WAITINGQUEUE_SETUP

            RK_ERR err = kTimeoutNodeAdd(&RK_gRunPtr->timeoutNode, timeout);
            if (err != RK_ERR_SUCCESS)
            {
                RK_gRunPtr->timeoutNode.timeoutType = 0;
                RK_gRunPtr->timeoutNode.waitingQueuePtr = NULL;
                kTraceRecordObject(kobj, RK_TRACE_OP_PEND, err,
                                   kobj->waitingQueue.size);
                RK_CR_EXIT
                return (err);
            }
        }
        RK_gRunPtr->status = RK_BLOCKED;
        kTraceRecordObject(kobj, RK_TRACE_OP_PEND_BLOCK, RK_ERR_SUCCESS,
                           kobj->waitingQueue.size + 1UL);
        kTCBQEnqByPrio(&kobj->waitingQueue, RK_gRunPtr);
        kPendCtxSwtch();
        RK_CR_EXIT
        RK_CR_ENTER
        if (RK_gRunPtr->timeOut)
        {
            RK_gRunPtr->timeOut = RK_FALSE;
            kTraceRecordObject(kobj, RK_TRACE_OP_TIMEOUT, RK_ERR_TIMEOUT,
                               kobj->waitingQueue.size);
            RK_CR_EXIT
            return (RK_ERR_TIMEOUT);
        }

        if ((timeout != RK_WAIT_FOREVER) && (timeout > 0) &&
            (RK_gRunPtr->timeoutNode.timeoutType == RK_TIMEOUT_BLOCKING))
        {
            kRemoveTimeoutNode(&RK_gRunPtr->timeoutNode);
            RK_gRunPtr->timeoutNode.timeoutType = 0;
            RK_gRunPtr->timeoutNode.waitingQueuePtr = NULL;
        }
    }
    kTraceRecordObject(kobj, RK_TRACE_OP_PEND, RK_ERR_SUCCESS, kobj->value);
    RK_CR_EXIT
    return (RK_ERR_SUCCESS);
}

RK_ERR kSemaphorePost(RK_SEMAPHORE *const kobj)
{
    RK_CR_AREA
    RK_CR_ENTER

#if (RK_CONF_ERR_CHECK == ON)

    if (kobj == NULL)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NULL);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NULL);
    }

    if (kobj->objID != RK_SEMAPHORE_KOBJ_ID)
    {
        K_ERR_HANDLER(RK_FAULT_INVALID_OBJ);
        RK_CR_EXIT
        return (RK_ERR_INVALID_OBJ);
    }

    if (kobj->init == RK_FALSE)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NOT_INIT);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NOT_INIT);
    }

#endif

    RK_TCB *nextTCBPtr = NULL;
    RK_ERR ret = -1;
    if (kobj->waitingQueue.size > 0)
    {
        kTCBQDeq(&(kobj->waitingQueue), &nextTCBPtr);
        if (nextTCBPtr->timeoutNode.timeoutType == RK_TIMEOUT_BLOCKING)
        {
            kRemoveTimeoutNode(&nextTCBPtr->timeoutNode);
            nextTCBPtr->timeoutNode.timeoutType = 0;
            nextTCBPtr->timeoutNode.waitingQueuePtr = NULL;
        }
        ret = kReadySwtch(nextTCBPtr);
        kTraceRecordObject(kobj, RK_TRACE_OP_WAKE, ret,
                           kobj->waitingQueue.size);
    }
    else
    {
        /* there are no waiting tasks */
        if (K_SEMA_IS_BINARY(kobj))
        {
            if (kobj->value != 0U)
            {
                ret = RK_ERR_SEMA_FULL;
            }
            else
            {
                kobj->value = 1U;
                ret = RK_ERR_SUCCESS;
            }
        }
        else
        {
            if (kobj->value == kobj->maxValue)
            {
                ret = RK_ERR_SEMA_FULL;
            }
            else
            {
                kobj->value += 1U;
                ret = RK_ERR_SUCCESS;
            }
        }
        kTraceRecordObject(kobj, RK_TRACE_OP_POST, ret, kobj->value);
    }
    RK_CR_EXIT
    return (ret);
}

RK_ERR kSemaphoreQuery(RK_SEMAPHORE const *const kobj, INT *const countPtr)
{

    RK_CR_AREA
    RK_CR_ENTER

#if (RK_CONF_ERR_CHECK == ON)

    if (kobj == NULL)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NULL);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NULL);
    }

    if (kobj->objID != RK_SEMAPHORE_KOBJ_ID)
    {
        K_ERR_HANDLER(RK_FAULT_INVALID_OBJ);
        RK_CR_EXIT
        return (RK_ERR_INVALID_OBJ);
    }

    if (kobj->init == RK_FALSE)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NOT_INIT);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NOT_INIT);
    }

    if (countPtr == NULL)
    {
        K_ERR_HANDLER(RK_FAULT_OBJ_NULL);
        RK_CR_EXIT
        return (RK_ERR_OBJ_NULL);
    }

#endif

    if (kobj->waitingQueue.size > 0)
    {
        INT retVal = (-((INT)kobj->waitingQueue.size));
        *countPtr = retVal;
    }
    else
    {
        *countPtr = (INT)kobj->value;
    }
    RK_CR_EXIT
    return (RK_ERR_SUCCESS);
}

#endif
