/*
 * Copyright 2020 NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#include <stdarg.h>
#include <stdlib.h>

#include "fsl_common.h"
#include "fsl_str.h"
#include "fsl_component_log.h"

#ifdef SDK_OS_FREE_RTOS
#include "FreeRTOS.h"
#include "semphr.h"
#endif

/*******************************************************************************
 * Definitions
 ******************************************************************************/

#if (LOG_ENABLE_ASYNC_MODE > 0)
/* MAX argument count is fixed count + user's count */
#define LOG_MAX_ACTUAL_ARGUMENT_COUNT (LOG_MAX_ARGUMENT_COUNT + 2)

#define LOG_GEN_ARG(n, i)         (busy->argv[n - (i)])
#define LOG_GEN_ARGUMENT_GET_N(n) n

#define LOG_GEN_ARGUMENT_1(n, i)  LOG_GEN_ARG(n, i)
#define LOG_GEN_ARGUMENT_2(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_1(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_3(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_2(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_4(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_3(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_5(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_4(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_6(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_5(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_7(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_6(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_8(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_7(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_9(n, i)  LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_8(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_10(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_9(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_11(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_10(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_12(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_11(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_13(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_12(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_14(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_13(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_15(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_14(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_16(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_15(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_17(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_16(n, LOG_GEN_ARGUMENT_GET_N(i - 1))
#define LOG_GEN_ARGUMENT_18(n, i) LOG_GEN_ARG(n, i), LOG_GEN_ARGUMENT_17(n, LOG_GEN_ARGUMENT_GET_N(i - 1))

#define _LOG_GEN_ARGUMENT_CAT(macro, sub) macro##sub
#define LOG_GEN_ARGUMENT_CAT(macro, sub)  _LOG_GEN_ARGUMENT_CAT(macro, sub)
#define LOG_GEN_ARGUMENT(n)               LOG_GEN_ARGUMENT_CAT(LOG_GEN_ARGUMENT_, LOG_GEN_ARGUMENT_GET_N(n))((n - 1), (n - 1))
#endif

#if (LOG_ENABLE_ASYNC_MODE > 0)
typedef struct log_string_node
{
    struct log_string_node *next;
    LOG_ARGUMENT_TYPE argv[LOG_MAX_ACTUAL_ARGUMENT_COUNT];
    char const *formatString;
    unsigned int timeStamp;
    log_level_t level;
    uint32_t argc;
} log_string_node_t;
#endif

typedef struct log_print_buffer
{
    uint8_t *buffer;
    size_t length;
    size_t sofar;
} log_print_buffer_t;

typedef struct log_context
{
    log_backend_t *backend;
#if (LOG_ENABLE_ASYNC_MODE > 0)
    log_print_buffer_t printBuffer;
#endif
#if LOG_ENABLE_TIMESTAMP
    log_get_timestamp_callback_t getTimeStamp;
#endif
#ifdef SDK_OS_FREE_RTOS
    SemaphoreHandle_t mutex;
#endif
#if (LOG_ENABLE_ASYNC_MODE > 0)
    log_string_node_t stringNode[LOG_MAX_BUFF_LOG_COUNT];
    log_string_node_t *idle;
    log_string_node_t *pend;
#if !(LOG_ENABLE_OVERWRITE > 0)
    SemaphoreHandle_t idleCount;
#endif
#endif
    uint8_t initialized;
} log_context_t;

#if LOG_ENABLE_COLOR
#define LOG_COLOR_CODE_DEFAULT \
    "\x1B"                     \
    "[0m"
#define LOG_COLOR_CODE_MAGENTA \
    "\x1B"                     \
    "[1;35m"
#define LOG_COLOR_CODE_MAGENTA_BG \
    "\x1B"                        \
    "[0;35m"
#define LOG_COLOR_CODE_RED \
    "\x1B"                 \
    "[1;31m"
#define LOG_COLOR_CODE_RED_BG \
    "\x1B"                    \
    "[0;31m"
#define LOG_COLOR_CODE_YELLOW \
    "\x1B"                    \
    "[1;33m"
#define LOG_COLOR_CODE_YELLOW_BG \
    "\x1B"                       \
    "[0;33m"
#define LOG_COLOR_CODE_GREEN \
    "\x1B"                   \
    "[1;32m"
#define LOG_COLOR_CODE_GREEN_BG \
    "\x1B"                      \
    "[0;32m"
#define LOG_COLOR_CODE_CYAN \
    "\x1b"                  \
    "[1;36m"
#define LOG_COLOR_CODE_CYAN_BG \
    "\x1b"                     \
    "[0;36m"
#define LOG_COLOR_CODE_BLUE \
    "\x1B"                  \
    "[1;34m"
#define LOG_COLOR_CODE_BLUE_BG \
    "\x1B"                     \
    "[0;34m"

#define LOG_COLOR_PRINT                  " %s%s%s "
#define LOG_COLOR_PRINT_PARAMETER(level) log_get_color(level), log_get_level_name(level), log_get_color(kLOG_LevelNone)
#else
#define LOG_COLOR_PRINT                  " %s "
#define LOG_COLOR_PRINT_PARAMETER(level) log_get_level_name(level)
#endif

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
 * Variables
 ******************************************************************************/

static log_context_t s_logContext;
#if LOG_ENABLE_COLOR
static const char *s_logColor[] = {
    LOG_COLOR_CODE_DEFAULT, LOG_COLOR_CODE_MAGENTA, LOG_COLOR_CODE_RED,  LOG_COLOR_CODE_YELLOW,
    LOG_COLOR_CODE_GREEN,   LOG_COLOR_CODE_CYAN,    LOG_COLOR_CODE_BLUE,
};
#endif
static const char *s_logLevelName[] = {
    "None", "FATAL", "ERROR", "WARN ", "INFO ", "DEBUG", "TRACE",
};

/*******************************************************************************
 * Code
 ******************************************************************************/
static void log_output_low_level(uint8_t *buffer, size_t length)
{
    log_backend_t *backend = s_logContext.backend;

    while (NULL != backend)
    {
        if (NULL != backend->putStr)
        {
            backend->putStr(buffer, length);
        }
        backend = backend->next;
    }
}

static void log_print_to_buffer(char *buf, int32_t *indicator, char val, int len)
{
    log_print_buffer_t *buffer = (log_print_buffer_t *)((void *)((size_t)buf));
    int i                      = 0;

    for (i = 0; i < len; i++)
    {
        if ((((size_t)(*indicator)) + buffer->sofar + ((size_t)1)) >= buffer->length)
        {
            break;
        }

        if ((val == '\n') &&
            ((0 == (*indicator)) ||
             (((uint8_t)'\r') != buffer->buffer[((size_t)(*indicator)) + buffer->sofar - ((size_t)1)])))
        {
            buffer->buffer[((size_t)(*indicator)) + buffer->sofar] = (uint8_t)'\r';
            (*indicator)++;
        }

        buffer->buffer[((size_t)(*indicator)) + buffer->sofar] = (uint8_t)val;
        (*indicator)++;
    }
}

static int log_sprintf_internal(log_print_buffer_t *printBuf, char const *format, ...)
{
    va_list ap;
    int ret;

    va_start(ap, format);
    /* format print log first */
    ret = StrFormatPrintf(format, ap, (char *)printBuf, log_print_to_buffer);
    va_end(ap);

    return ret;
}

static char const *log_get_level_name(log_level_t level)
{
    assert(((uint8_t)level < ARRAY_SIZE(s_logLevelName)));
    return s_logLevelName[(int)level];
}

#if LOG_ENABLE_COLOR
static char const *log_get_color(log_level_t level)
{
    assert(((uint8_t)level < ARRAY_SIZE(s_logColor)));
    return s_logColor[(int)level];
}
#endif

#if (LOG_ENABLE_ASYNC_MODE > 0)
static int LOG_DumpInternal(void)
{
    log_string_node_t *busy;
    uint32_t primask;
    int length = -1;

    primask = DisableGlobalIRQ();
    busy    = s_logContext.pend;
    if (NULL != s_logContext.pend)
    {
        s_logContext.pend = s_logContext.pend->next;
    }
    EnableGlobalIRQ(primask);

    if (NULL != busy)
    {
        switch (busy->argc)
        {
            case 2:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(2));
                break;
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 2
            case 3:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(3));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 3
            case 4:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(4));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 4
            case 5:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(5));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 5
            case 6:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(6));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 6
            case 7:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(7));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 7
            case 8:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(8));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 8
            case 9:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(9));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 9
            case 10:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(10));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 10
            case 11:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(11));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 11
            case 12:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(12));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 12
            case 13:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(13));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 13
            case 14:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(14));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 14
            case 15:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(15));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 15
            case 16:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(16));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 16
            case 17:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(17));
                break;
#endif
#if LOG_MAX_ACTUAL_ARGUMENT_COUNT > 17
            case 18:
                LOG_Printf(NULL, busy->level, busy->timeStamp, (const char *)busy->formatString, LOG_GEN_ARGUMENT(18));
                break;
#endif
            default:
                /* Fix MISRA C-2012 Rule 16.4 */
                break;
        }

        primask           = DisableGlobalIRQ();
        busy->next        = s_logContext.idle;
        s_logContext.idle = busy;
#if !(LOG_ENABLE_OVERWRITE > 0)
        (void)xSemaphoreGive(s_logContext.idleCount);
#endif
        EnableGlobalIRQ(primask);
    }
    return length;
}
#endif

log_status_t LOG_Init(void)
{
#if (LOG_ENABLE_ASYNC_MODE > 0)
    uint8_t i;
#endif

    if (0U != s_logContext.initialized)
    {
        return kStatus_LOG_Initialized;
    }

    (void)memset(&s_logContext, 0, sizeof(s_logContext));
#if (LOG_ENABLE_ASYNC_MODE > 0)
    s_logContext.idle = &s_logContext.stringNode[0];
    for (i = 1U; i < ((uint8_t)LOG_MAX_BUFF_LOG_COUNT); i++)
    {
        s_logContext.stringNode[i].next = s_logContext.idle;
        s_logContext.idle               = &s_logContext.stringNode[i];
    }
#if !(LOG_ENABLE_OVERWRITE > 0)
    s_logContext.idleCount = xSemaphoreCreateCounting(LOG_MAX_BUFF_LOG_COUNT, LOG_MAX_BUFF_LOG_COUNT);
    if (NULL == s_logContext.idleCount)
    {
        return kStatus_LOG_LackResource;
    }
#endif
#endif

#ifdef SDK_OS_FREE_RTOS
    s_logContext.mutex = xSemaphoreCreateRecursiveMutex();
    if (NULL == s_logContext.mutex)
    {
        return kStatus_LOG_LackResource;
    }
#endif
    s_logContext.initialized = 1U;

    return kStatus_LOG_Success;
}

log_status_t LOG_Deinit(void)
{
    if (0U == s_logContext.initialized)
    {
        return kStatus_LOG_Uninitialized;
    }
#ifdef SDK_OS_FREE_RTOS
    if (NULL != s_logContext.mutex)
    {
        vSemaphoreDelete(s_logContext.mutex);
        s_logContext.mutex = NULL;
    }
#endif
    s_logContext.initialized = 0U;
    return kStatus_LOG_Success;
}

#if LOG_ENABLE_TIMESTAMP
log_status_t LOG_SetTimestamp(log_get_timestamp_callback_t getTimeStamp)
{
    if (0U == s_logContext.initialized)
    {
        return kStatus_LOG_Uninitialized;
    }

    s_logContext.getTimeStamp = getTimeStamp;

    return kStatus_LOG_Success;
}

unsigned int LOG_GetTimestamp(void)
{
    if (0U == s_logContext.initialized)
    {
        return 0;
    }

    if (NULL != s_logContext.getTimeStamp)
    {
        return s_logContext.getTimeStamp();
    }
    return 0;
}
#endif

#if (LOG_ENABLE_ASYNC_MODE > 0)
void LOG_AsyncPrintf(log_module_t const *module,
                     log_level_t level,
                     unsigned int timeStamp,
                     char const *format,
                     uint32_t argc,
                     LOG_ARGUMENT_TYPE argv[])
{
    log_string_node_t *node = NULL;
    log_string_node_t *p    = NULL;
    uint32_t primask;

    assert((((uint32_t)LOG_MAX_ACTUAL_ARGUMENT_COUNT) >= argc));

    do
    {
        primask = DisableGlobalIRQ();
        if (NULL != s_logContext.idle)
        {
            node              = s_logContext.idle;
            s_logContext.idle = s_logContext.idle->next;
        }
        EnableGlobalIRQ(primask);

        if (NULL == node)
        {
#if (LOG_ENABLE_OVERWRITE > 0)
            primask = DisableGlobalIRQ();
            node    = s_logContext.pend;
            if (NULL != s_logContext.pend)
            {
                s_logContext.pend = s_logContext.pend->next;
            }
            EnableGlobalIRQ(primask);
            if (NULL == node)
            {
                break;
            }
#else
            (void)xSemaphoreTake(s_logContext.idleCount, (TickType_t)10);
            primask = DisableGlobalIRQ();
            if (NULL != s_logContext.idle)
            {
                node              = s_logContext.idle;
                s_logContext.idle = s_logContext.idle->next;
            }
            EnableGlobalIRQ(primask);
#endif
        }
    } while (NULL == node);

#if (LOG_ENABLE_OVERWRITE > 0)
    if (NULL != node)
#endif
    {
        node->formatString = format;
        node->timeStamp    = timeStamp;
        node->level        = level;
        node->argc         = argc;
        node->next         = NULL;
        (void)memcpy(node->argv, argv, sizeof(argv[0]) * argc);

        if (NULL != s_logContext.pend)
        {
            p = s_logContext.pend;
            while (NULL != p->next)
            {
                p = p->next;
            }
            p->next = node;
        }
        else
        {
            s_logContext.pend = node;
        }
    }
}
#endif

void LOG_Printf(log_module_t const *module, log_level_t level, unsigned int timeStamp, char const *format, ...)
{
    va_list ap;
    log_print_buffer_t buffer;
    uint8_t printBuf[LOG_MAX_MEESSAGE_LENGTH];

    (void)module;

    if (0U == s_logContext.initialized)
    {
        return;
    }

#ifdef SDK_OS_FREE_RTOS
    if (pdFALSE == xSemaphoreTakeRecursive(s_logContext.mutex, portMAX_DELAY))
    {
        return;
    }
#endif

#if (LOG_ENABLE_ASYNC_MODE > 0)
    if (NULL != s_logContext.printBuffer.buffer)
    {
        (void)memcpy(&buffer, &s_logContext.printBuffer, sizeof(buffer));
    }
    else
#endif
    {
        buffer.buffer = printBuf;
        buffer.length = LOG_MAX_MEESSAGE_LENGTH;
        buffer.sofar  = 0;
    }

    /* print timestamp */
#if LOG_ENABLE_TIMESTAMP
    buffer.sofar += (size_t)log_sprintf_internal(&buffer, "%12d:", timeStamp);
#endif
    /* print level name */
    buffer.sofar += (size_t)log_sprintf_internal(&buffer, LOG_COLOR_PRINT, LOG_COLOR_PRINT_PARAMETER(level));

    va_start(ap, format);
    /* format print log first */
    buffer.sofar += (size_t)StrFormatPrintf(format, ap, (char *)&buffer, log_print_to_buffer);

    va_end(ap);
#if (LOG_ENABLE_ASYNC_MODE > 0)
    s_logContext.printBuffer.sofar = buffer.sofar;
#endif
    log_output_low_level(buffer.buffer, buffer.sofar);

#ifdef SDK_OS_FREE_RTOS
    if (pdFALSE == xSemaphoreGiveRecursive(s_logContext.mutex))
    {
        return;
    }
#endif
}

log_status_t LOG_BackendRegister(log_backend_t *backend)
{
    log_backend_t *p = s_logContext.backend;
    log_status_t ret = kStatus_LOG_BackendExist;
    uint32_t regPrimask;

    assert(NULL != backend);

    if (0U == s_logContext.initialized)
    {
        return kStatus_LOG_Uninitialized;
    }

    regPrimask = DisableGlobalIRQ();
    while (NULL != p)
    {
        if (p == backend)
        {
            break;
        }
        p = p->next;
    }
    if (NULL == p)
    {
        backend->next        = s_logContext.backend;
        s_logContext.backend = backend;
        ret                  = kStatus_LOG_Success;
    }
    EnableGlobalIRQ(regPrimask);
    return ret;
}

log_status_t LOG_BackendUnregister(log_backend_t *backend)
{
    log_backend_t *p = s_logContext.backend;
    log_backend_t *q = s_logContext.backend;
    log_status_t ret = kStatus_LOG_BackendNotFound;
    uint32_t regPrimask;

    assert(NULL != backend);

    if (0U == s_logContext.initialized)
    {
        return kStatus_LOG_Uninitialized;
    }

    regPrimask = DisableGlobalIRQ();
    while (NULL != p)
    {
        if (p == backend)
        {
            if (q == p)
            {
                s_logContext.backend = p->next;
            }
            else
            {
                q->next = p->next;
            }
            p->next = NULL;
            ret     = kStatus_LOG_Success;
        }
        q = p;
        p = p->next;
    }
    EnableGlobalIRQ(regPrimask);

    return ret;
}

#if (LOG_ENABLE_ASYNC_MODE > 0)
void LOG_Dump(uint8_t *buffer, size_t length, size_t *outLength)
{
    s_logContext.printBuffer.buffer = buffer;
    s_logContext.printBuffer.length = length;
    s_logContext.printBuffer.sofar  = (size_t)0;
    (void)LOG_DumpInternal();
    if ((NULL != buffer) && (((size_t)0) != length) && (NULL != outLength))
    {
        *outLength = s_logContext.printBuffer.sofar;
    }
}
#endif
