/*
 * The Clear BSD License
 * Copyright 2017 NXP
 * All rights reserved.
 *
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted (subject to the limitations in the disclaimer below) provided
 *  that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "fsl_log.h"
#include "fsl_debug_console_conf.h"
#include "fsl_io.h"
#ifdef FSL_RTOS_FREE_RTOS
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#endif
/*******************************************************************************
 * Definitions
 ******************************************************************************/
#ifndef BACKSPACE
/*! @brief character backspace ASCII value */
#define BACKSPACE 127
#endif

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
/*! @brief increase pop member */
#define LOG_CHECK_BUFFER_INDEX_OVERFLOW(index)          \
    {                                                   \
        if (index >= DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN) \
        {                                               \
            index -= DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN; \
        }                                               \
    \
\
}

/*! @brief get current runing environment is ISR or not */
#ifdef __CA7_REV
#define IS_RUNNING_IN_ISR() SystemGetIRQNestingLevel()
#else
#define IS_RUNNING_IN_ISR() __get_IPSR()
#endif /* __CA7_REV */

#else
#define IS_RUNNING_IN_ISR() (0U)
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

/* define for rtos */
#if (DEBUG_CONSOLE_SYNCHRONIZATION_MODE == DEBUG_CONSOLE_SYNCHRONIZATION_FREERTOS)
/* metex semaphore */
#define LOG_CREATE_MUTEX_SEMAPHORE(mutex) (mutex = xSemaphoreCreateMutex())

#define LOG_GIVE_MUTEX_SEMAPHORE(mutex) \
    \
{                                \
        if (IS_RUNNING_IN_ISR() == 0U)  \
        {                               \
            xSemaphoreGive(mutex);      \
        }                               \
    \
}

#define LOG_TAKE_MUTEX_SEMAPHORE_BLOCKING(mutex)  \
    \
{                                          \
        if (IS_RUNNING_IN_ISR() == 0U)            \
        {                                         \
            xSemaphoreTake(mutex, portMAX_DELAY); \
        }                                         \
    \
}

#define LOG_TAKE_MUTEX_SEMAPHORE_NONBLOCKING(mutex, result) \
    \
{                                                    \
        if (IS_RUNNING_IN_ISR() == 0U)                      \
        {                                                   \
            result = xSemaphoreTake(mutex, 0U);             \
        }                                                   \
        else                                                \
        {                                                   \
            result = 1U;                                    \
        }                                                   \
    \
}

/* Binary semaphore */
#define LOG_CREATE_BINARY_SEMAPHORE(binary) (binary = xSemaphoreCreateBinary())
#define LOG_TAKE_BINARY_SEMAPHORE_BLOCKING(binary) (xSemaphoreTake(binary, portMAX_DELAY))
#define LOG_GIVE_BINARY_SEMAPHORE_FROM_ISR(binary) (xSemaphoreGiveFromISR(binary, NULL))

#elif(DEBUG_CONSOLE_SYNCHRONIZATION_MODE == DEBUG_CONSOLE_SYNCHRONIZATION_BM)

#define LOG_CREATE_MUTEX_SEMAPHORE(mutex)
#define LOG_TAKE_MUTEX_SEMAPHORE_BLOCKING(mutex)
#define LOG_GIVE_MUTEX_SEMAPHORE(mutex)
#define LOG_CREATE_BINARY_SEMAPHORE(binary)
#define LOG_TAKE_MUTEX_SEMAPHORE_NONBLOCKING(mutex, result) (result = 1U)
#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
#define LOG_TAKE_BINARY_SEMAPHORE_BLOCKING(binary) \
    \
{                                           \
        while (!binary)                            \
            ;                                      \
        binary = false;                            \
    \
\
}
#define LOG_GIVE_BINARY_SEMAPHORE_FROM_ISR(binary) (binary = true)
#else
#define LOG_TAKE_BINARY_SEMAPHORE_BLOCKING(binary)
#define LOG_GIVE_BINARY_SEMAPHORE_FROM_ISR(binary)
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

/* add other implementation here
*such as :
* #elif(DEBUG_CONSOLE_SYNCHRONIZATION_MODE == DEBUG_CONSOLE_SYNCHRONIZATION_xxx)
*/

#else

#define LOG_CREATE_MUTEX_SEMAPHORE(mutex)
#define LOG_TAKE_MUTEX_SEMAPHORE_BLOCKING(mutex)
#define LOG_TAKE_MUTEX_SEMAPHORE_NONBLOCKING(mutex, result) (result = 1U)
#define LOG_GIVE_MUTEX_SEMAPHORE(mutex)
#define LOG_CREATE_BINARY_SEMAPHORE(binary)
#define LOG_TAKE_BINARY_SEMAPHORE_BLOCKING(binary)
#endif /* DEBUG_CONSOLE_SYNCHRONIZATION_MODE == DEBUG_CONSOLE_SYNCHRONIZATION_FREERTOS */

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
/*! @brief Define the buffer
* The total buffer size should be calculate as (BUFFER_SUPPORT_LOG_LENGTH + 1) * BUFFER_SUPPORT_LOG_NUM * 4
*/
typedef struct _log_buffer
{
    volatile uint16_t totalIndex;                     /*!< indicate the total usage of the buffer */
    volatile uint16_t pushIndex;                      /*!< indicate the next push index */
    volatile uint16_t popIndex;                       /*!< indicate the pop index */
    uint8_t txBuf[DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN]; /*!< buffer to store printf log */

    uint8_t rxBuf[DEBUG_CONSOLE_RECEIVE_BUFFER_LEN]; /*!< buffer to store scanf log */
} log_buffer_t;
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

/*******************************************************************************
 * Variables
 ******************************************************************************/
#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
/* A global log buffer */
static log_buffer_t s_log_buffer;
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

/* lock definition */
#if (DEBUG_CONSOLE_SYNCHRONIZATION_MODE == DEBUG_CONSOLE_SYNCHRONIZATION_FREERTOS)
#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
static SemaphoreHandle_t s_logPushSemaphore = NULL;
static SemaphoreHandle_t s_logReadSemaphore = NULL;
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */
static SemaphoreHandle_t s_logPopSemaphore = NULL;
static SemaphoreHandle_t s_logReadWaitSemaphore = NULL;

#elif(DEBUG_CONSOLE_SYNCHRONIZATION_MODE == DEBUG_CONSOLE_SYNCHRONIZATION_BM)

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING

static volatile bool s_logReadWaitSemaphore = false; /* transferred event from ISR for bare-metal + interrupt */

#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

#else
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

/*******************************************************************************
* Prototypes
******************************************************************************/
#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
/*!
 * @brief callback function for IO layer to notify LOG
 *
 * @param size last transfer data size
 * @param receive indicate a RX transfer
 * @param transmit indicate a TX transfer
 *
 */
static void LOG_Transferred(size_t *size, bool receive, bool transmit);

/*!
 * @brief log push function
 *
 * @param buf target buffer
 * @param size log size
 *
 */
static int LOG_BufPush(uint8_t *buf, size_t size);

/*!
 * @brief Get next available log
 *
 * @param next available size
 * @return next available address
 */
static uint8_t *LOG_BufGetNextAvailableLog(size_t *size);

/*!
 * @brief buf pop
 *
 * @param size log size popped and next available log size
 * @return next available address
 */
static uint8_t *LOG_BufPop(size_t *size);

#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

/*!
 * @brief read one character
 *
 * @param ch character address
 * @return indicate the read status
 *
 */
static status_t LOG_ReadOneCharacter(uint8_t *ch);

#if DEBUG_CONSOLE_ENABLE_ECHO_FUNCTION
/*!
 * @brief echo one character
 *
 * @param ch character address
 * @param isGetchar flag to distinguish getchar from scanf
 * @param index special for scanf to support backspace
 * @return indicate the read status
 *
 */
static status_t LOG_EchoCharacter(uint8_t *ch, bool isGetChar, int *index);
#endif

/*******************************************************************************
 * Code
 ******************************************************************************/
status_t LOG_Init(uint32_t baseAddr, uint8_t device, uint32_t baudRate, uint32_t clkSrcFreq)
{
    io_state_t io;
    /* init io */
    io.ioBase = (void *)baseAddr;
    io.ioType = device;

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
    /* memset the global queue */
    memset(&s_log_buffer, 0U, sizeof(s_log_buffer));
    /* init callback for NON-BLOCKING */
    io.callBack = LOG_Transferred;
    /* io init function */
    IO_Init(&io, baudRate, clkSrcFreq, s_log_buffer.rxBuf);
    /* Debug console buffer push lock create */
    LOG_CREATE_MUTEX_SEMAPHORE(s_logPushSemaphore);
    /* Debug console get/scanf mutex lock create */
    LOG_CREATE_MUTEX_SEMAPHORE(s_logReadSemaphore);
#else
    IO_Init(&io, baudRate, clkSrcFreq, NULL);
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

    /* Debug console lock create */
    LOG_CREATE_MUTEX_SEMAPHORE(s_logPopSemaphore);
    LOG_CREATE_BINARY_SEMAPHORE(s_logReadWaitSemaphore);

    return kStatus_Success;
}

void LOG_Deinit(void)
{
#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
    /* memset the global queue */
    memset(&s_log_buffer, 0U, sizeof(s_log_buffer));
#endif /*DEBUG_CONSOLE_TRANSFER_NON_BLOCKING*/
    /* Deinit IO */
    IO_Deinit();
}

status_t LOG_WaitIdle(void)
{
#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
    /* wait buffer empty */
    while (!(s_log_buffer.totalIndex == 0U))
        ;
#endif /*DEBUG_CONSOLE_TRANSFER_NON_BLOCKING*/
    /* wait IO idle */
    IO_WaitIdle();

    return kStatus_Success;
}

int LOG_Push(uint8_t *buf, size_t size)
{
    assert(buf != NULL);

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
    /* push to buffer */
    LOG_BufPush(buf, size);
    buf = LOG_BufGetNextAvailableLog(&size);
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */
    /* pop log */
    return LOG_Pop(buf, size);
}

int LOG_Pop(uint8_t *buf, size_t size)
{
    uint8_t getLock = 0U;

    if ((0 != size) && (NULL != buf))
    {
        /* take POP lock, should be non-blocking */
        LOG_TAKE_MUTEX_SEMAPHORE_NONBLOCKING(s_logPopSemaphore, getLock);

        if (getLock)
        {
            /* call IO transfer function */
            if (IO_Transfer(buf, size, true) != kStatus_Success)
            {
                size = 0U;
            }
            /* release POP lock */
            LOG_GIVE_MUTEX_SEMAPHORE(s_logPopSemaphore);
        }
    }

    return size;
}

int LOG_ReadLine(uint8_t *buf, size_t size)
{
    assert(buf != NULL);

    int i = 0;

    /* take mutex lock function */
    LOG_TAKE_MUTEX_SEMAPHORE_BLOCKING(s_logReadSemaphore);

    for (i = 0; i < size; i++)
    {
        /* receive one char every time */
        if (LOG_ReadOneCharacter(&buf[i]) != kStatus_Success)
        {
            return -1;
        }
#if DEBUG_CONSOLE_ENABLE_ECHO_FUNCTION
        LOG_EchoCharacter(&buf[i], false, &i);
#endif
        /* analysis data */
        if ((buf[i] == '\r') || (buf[i] == '\n'))
        {
            /* End of Line. */
            if (i == 0)
            {
                buf[i] = '\0';
                i = -1;
            }
            else
            {
                break;
            }
        }
    }
    /* get char should not add '\0'*/
    if (i == size)
    {
        buf[i] = '\0';
    }
    else
    {
        buf[i + 1] = '\0';
    }

    /* release mutex lock function */
    LOG_GIVE_MUTEX_SEMAPHORE(s_logReadSemaphore);

    return i;
}

int LOG_ReadCharacter(uint8_t *ch)
{
    assert(ch != NULL);
    int ret = 0;

    /* take mutex lock function */
    LOG_TAKE_MUTEX_SEMAPHORE_BLOCKING(s_logReadSemaphore);
    /* read one character */
    if (LOG_ReadOneCharacter(ch) == kStatus_Success)
    {
        ret = 1;
#if DEBUG_CONSOLE_ENABLE_ECHO_FUNCTION
        LOG_EchoCharacter(ch, true, NULL);
#endif
    }
    else
    {
        ret = -1;
    }

    /* release mutex lock function */
    LOG_GIVE_MUTEX_SEMAPHORE(s_logReadSemaphore);

    return ret;
}

static status_t LOG_ReadOneCharacter(uint8_t *ch)
{
    /* receive one char every time */
    if (IO_Transfer(ch, 1U, false) != kStatus_Success)
    {
        return kStatus_Fail;
    }

    /* wait release from ISR */
    LOG_TAKE_BINARY_SEMAPHORE_BLOCKING(s_logReadWaitSemaphore);

    return kStatus_Success;
}

#if DEBUG_CONSOLE_ENABLE_ECHO_FUNCTION
static status_t LOG_EchoCharacter(uint8_t *ch, bool isGetChar, int *index)
{
    /* Due to scanf take \n and \r as end of string,should not echo */
    if (((*ch != '\r') && (*ch != '\n')) || (isGetChar))
    {
        /* receive one char every time */
        if (IO_Transfer(ch, 1U, true) != kStatus_Success)
        {
            return kStatus_Fail;
        }
    }

    if (!isGetChar)
    {
        if ((*index > 0) && (*ch == BACKSPACE))
        {
            *index -= 2;
        }
    }

    return kStatus_Success;
}
#endif

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
static int LOG_BufPush(uint8_t *buf, size_t size)
{
    uint32_t pushIndex = 0U, i = 0U;
    bool pushAvailable = false;

    /* take mutex lock function */
    LOG_TAKE_MUTEX_SEMAPHORE_BLOCKING(s_logPushSemaphore);
    if (size <= (DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN - s_log_buffer.totalIndex))
    {
        /* get push index */
        pushIndex = s_log_buffer.pushIndex;
        s_log_buffer.pushIndex += size;
        /* check index overflow */
        LOG_CHECK_BUFFER_INDEX_OVERFLOW(s_log_buffer.pushIndex);
        /* update push/total index value */
        s_log_buffer.totalIndex += size;
        pushAvailable = true;
    }
    /* release mutex lock function */
    LOG_GIVE_MUTEX_SEMAPHORE(s_logPushSemaphore);

    /* check the buffer if have enough space to store the log */
    if (pushAvailable)
    {
        for (i = size; i > 0; i--)
        {
            /* copy log to buffer, the buffer only support a fixed length argument, if the log argument
            is longer than the fixed length, the left argument will be lost */
            s_log_buffer.txBuf[pushIndex] = *buf++;
            /* increase index */
            pushIndex++;
            /* check index overflow */
            LOG_CHECK_BUFFER_INDEX_OVERFLOW(pushIndex);
        }
    }
    else
    {
        size = 0U;
    }

    return size;
}

static uint8_t *LOG_BufGetNextAvailableLog(size_t *size)
{
    uint16_t popIndex = s_log_buffer.popIndex;

    /* get available size */
    if (s_log_buffer.totalIndex > (DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN - popIndex))
    {
        *size = (DEBUG_CONSOLE_TRANSMIT_BUFFER_LEN - popIndex);
    }
    else
    {
        *size = s_log_buffer.totalIndex;
    }

    /* return address */
    return (&(s_log_buffer.txBuf[popIndex]));
}

static uint8_t *LOG_BufPop(size_t *size)
{
    if (s_log_buffer.totalIndex >= *size)
    {
        /* decrease the log total member */
        s_log_buffer.totalIndex -= *size;
        /* there is more log in the queue to be pushed */
        if (s_log_buffer.totalIndex > 0U)
        {
            /* update the pop index */
            s_log_buffer.popIndex += *size;
            /* check index overflow */
            LOG_CHECK_BUFFER_INDEX_OVERFLOW(s_log_buffer.popIndex);

            return LOG_BufGetNextAvailableLog(size);
        }
        else
        {
            /* reset push and pop */
            s_log_buffer.popIndex = 0U;
            s_log_buffer.pushIndex = 0U;
            *size = 0U;
        }
    }

    return NULL;
}

static void LOG_Transferred(size_t *size, bool receive, bool transmit)
{
    uint8_t *addr = NULL;

    if (transmit)
    {
        addr = LOG_BufPop(size);
        /* continue pop log from buffer */
        LOG_Pop(addr, *size);
    }

    if (receive)
    {
        /* release from ISR */
        LOG_GIVE_BINARY_SEMAPHORE_FROM_ISR(s_logReadWaitSemaphore);
    }
}
#endif /* DEBUG_CONSOLE_TRANSFER_NON_BLOCKING */

#ifdef DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
status_t LOG_TryReadCharacter(uint8_t *ch)
{
    if (NULL != ch)
    {
        return IO_TryReceiveCharacter(ch);
    }
    return kStatus_Fail;
}
#endif
