/**
 * Copyright (c) 2016 - 2021, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, 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.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 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 "sdk_common.h"
#if NRF_MODULE_ENABLED(NRF_LOG)
#include "app_util.h"
#include "app_util_platform.h"
#include "nrf_log.h"
#include "nrf_log_internal.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_backend_interface.h"
#include "nrf_log_str_formatter.h"
#include "nrf_section.h"
#include "nrf_ringbuf.h"
#include "nrf_memobj.h"
#include "nrf_atomic.h"
#include <string.h>

STATIC_ASSERT((NRF_LOG_BUFSIZE % 4) == 0);
STATIC_ASSERT(IS_POWER_OF_TWO(NRF_LOG_BUFSIZE));

#define NRF_LOG_BUF_WORDS (NRF_LOG_BUFSIZE/4)

#if NRF_MODULE_ENABLED(FDS) && NRF_LOG_FILTERS_ENABLED
#define LOG_CONFIG_LOAD_STORE_ENABLED 1
#else
#define LOG_CONFIG_LOAD_STORE_ENABLED 0
#endif

#if NRF_LOG_BUF_WORDS < 32
#warning "NRF_LOG_BUFSIZE too small, significant number of logs may be lost."
#endif

NRF_MEMOBJ_POOL_DEF(log_mempool, NRF_LOG_MSGPOOL_ELEMENT_SIZE, NRF_LOG_MSGPOOL_ELEMENT_COUNT);
NRF_RINGBUF_DEF(m_log_push_ringbuf, NRF_LOG_STR_PUSH_BUFFER_SIZE);

#define NRF_LOG_BACKENDS_FULL           0xFF
#define NRF_LOG_FILTER_BITS_PER_BACKEND 3
#define NRF_LOG_MAX_BACKENDS           (32/NRF_LOG_FILTER_BITS_PER_BACKEND)
#define NRF_LOG_MAX_HEXDUMP            (NRF_LOG_MSGPOOL_ELEMENT_SIZE*NRF_LOG_MSGPOOL_ELEMENT_COUNT/2)
#define NRF_LOG_INVALID_BACKEND_U32    0xFFFFFFFF

/* Mask is extracted from the structure log_data_t to allow compile time initialization which
 * is needed to allow usage of the logger (logging) before logger is initialized.
 * It cannot be part of the log_data_t because some compilers would put whole log_data_t structure
 * into flash (including buffer) just because one field is initilized.
 */
static uint32_t m_buffer_mask =  NRF_LOG_BUF_WORDS - 1; // Size of buffer (must be power of 2) presented as mask

/**
 * brief An internal control block of the logger
 *
 * @note Circular buffer is using never cleared indexes and a mask. It means
 * that logger may break when indexes overflows. However, it is quite unlikely.
 * With rate of 1000 log entries with 2 parameters per second such situation
 * would happen after 12 days.
 */
typedef struct
{
    bool                      autoflush;
    uint32_t                  wr_idx;          // Current write index (never reset)
    uint32_t                  rd_idx;          // Current read index  (never_reset)
    uint32_t                  buffer[NRF_LOG_BUF_WORDS];
    nrf_log_timestamp_func_t  timestamp_func;  // A pointer to function that returns timestamp
    nrf_log_backend_t const * p_backend_head;
    nrf_atomic_flag_t         log_skipping;
    nrf_atomic_flag_t         log_skipped;
    nrf_atomic_u32_t          log_dropped_cnt;
} log_data_t;

static log_data_t   m_log_data;

NRF_LOG_MODULE_REGISTER();

// Helper macros for section variables.
#define NRF_LOG_DYNAMIC_SECTION_VARS_GET(i)        NRF_SECTION_ITEM_GET(log_dynamic_data, nrf_log_module_dynamic_data_t, (i))
#define NRF_LOG_FILTER_SECTION_VARS_GET(i)         NRF_SECTION_ITEM_GET(log_filter_data, nrf_log_module_filter_data_t, (i))

#define NRF_LOG_CONST_SECTION_VARS_GET(i)          NRF_SECTION_ITEM_GET(log_const_data, nrf_log_module_const_data_t, (i))
#define NRF_LOG_CONST_SECTION_VARS_COUNT           NRF_SECTION_ITEM_COUNT(log_const_data, nrf_log_module_const_data_t)

ret_code_t nrf_log_init(nrf_log_timestamp_func_t timestamp_func, uint32_t timestamp_freq)
{
    (void)NRF_LOG_ITEM_DATA_CONST(app);

    if (NRF_LOG_USES_TIMESTAMP && (timestamp_func == NULL))
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    m_log_data.autoflush    = NRF_LOG_DEFERRED ? false : true;

    if (NRF_LOG_USES_TIMESTAMP)
    {
        nrf_log_str_formatter_timestamp_freq_set(timestamp_freq);
        m_log_data.timestamp_func = timestamp_func;
    }

#ifdef UNIT_TEST
    m_buffer_mask = NRF_LOG_BUF_WORDS - 1;
#endif

    ret_code_t err_code = nrf_memobj_pool_init(&log_mempool);
    if (err_code != NRF_SUCCESS)
    {
        return err_code;
    }

    nrf_ringbuf_init(&m_log_push_ringbuf);

    uint32_t modules_cnt = NRF_LOG_CONST_SECTION_VARS_COUNT;
    uint32_t i;
    if (NRF_LOG_FILTERS_ENABLED)
    {
        uint32_t j;
        //sort modules by name
        for (i = 0; i < modules_cnt; i++)
        {
            uint32_t idx = 0;

            for (j = 0; j < modules_cnt; j++)
            {
                if  (i != j)
                {
                    char const * p_name0 = NRF_LOG_CONST_SECTION_VARS_GET(i)->p_module_name;
                    char const * p_name1 = NRF_LOG_CONST_SECTION_VARS_GET(j)->p_module_name;
                    if (strncmp(p_name0, p_name1, 20) > 0)
                    {
                        idx++;
                    }
                }

            }
            nrf_log_module_dynamic_data_t * p_module_ddata = NRF_LOG_DYNAMIC_SECTION_VARS_GET(i);
            p_module_ddata->order_idx = idx;
        }

        /* Initialize filters */
        for (i = 0; i < modules_cnt; i++)
        {
            nrf_log_module_dynamic_data_t * p_module_ddata = NRF_LOG_DYNAMIC_SECTION_VARS_GET(i);
            nrf_log_module_filter_data_t * p_module_filter = NRF_LOG_FILTER_SECTION_VARS_GET(i);
            p_module_ddata->filter = 0;
            p_module_filter->filter_lvls = 0;
        }
    }

    return NRF_SUCCESS;
}

uint32_t nrf_log_module_cnt_get(void)
{
    return NRF_LOG_CONST_SECTION_VARS_COUNT;
}

static ret_code_t module_idx_get(uint32_t * p_idx, bool ordered_idx)
{
    if (ordered_idx)
    {
        uint32_t module_cnt = nrf_log_module_cnt_get();
        uint32_t i;
        for (i = 0; i < module_cnt; i++)
        {
            nrf_log_module_dynamic_data_t * p_module_data = NRF_LOG_DYNAMIC_SECTION_VARS_GET(i);
            if (p_module_data->order_idx == *p_idx)
            {
                *p_idx = i;
                return NRF_SUCCESS;
            }
        }
        return NRF_ERROR_NOT_FOUND;
    }
    else
    {
        return NRF_SUCCESS;
    }
}
const char * nrf_log_module_name_get(uint32_t module_id, bool ordered_idx)
{
    if (module_idx_get(&module_id, ordered_idx) == NRF_SUCCESS)
    {
        nrf_log_module_const_data_t * p_module_data = NRF_LOG_CONST_SECTION_VARS_GET(module_id);
        return p_module_data->p_module_name;
    }
    else
    {
        return NULL;
    }
}

uint8_t nrf_log_color_id_get(uint32_t module_id, nrf_log_severity_t severity)
{
    nrf_log_module_const_data_t * p_module_data = NRF_LOG_CONST_SECTION_VARS_GET(module_id);
    uint8_t color_id;
    switch (severity)
    {
    case NRF_LOG_SEVERITY_ERROR:
        color_id = NRF_LOG_ERROR_COLOR;
        break;
    case NRF_LOG_SEVERITY_WARNING:
        color_id = NRF_LOG_WARNING_COLOR;
        break;
    case NRF_LOG_SEVERITY_INFO:
        color_id = p_module_data->info_color_id;
        break;
    case NRF_LOG_SEVERITY_DEBUG:
        color_id = p_module_data->debug_color_id;
        break;
    default:
        color_id = 0;
        break;
    }
    return color_id;
}

static uint16_t higher_lvl_get(uint32_t lvls)
{
    uint16_t top_lvl = 0;
    uint16_t tmp_lvl;
    uint32_t i;

    //Find highest level enabled by backends
    for (i = 0; i < (32/NRF_LOG_LEVEL_BITS); i+=NRF_LOG_LEVEL_BITS)
    {
        tmp_lvl = (uint16_t)BF_GET(lvls,NRF_LOG_LEVEL_BITS, i);
        if (tmp_lvl > top_lvl)
        {
            top_lvl = tmp_lvl;
        }
    }
    return top_lvl;
}

void nrf_log_module_filter_set(uint32_t backend_id, uint32_t module_id, nrf_log_severity_t severity)
{
    if (NRF_LOG_FILTERS_ENABLED)
    {
        nrf_log_module_dynamic_data_t * p_module_data = NRF_LOG_DYNAMIC_SECTION_VARS_GET(module_id);
        nrf_log_module_filter_data_t *  p_filter      = NRF_LOG_FILTER_SECTION_VARS_GET(module_id);

        p_filter->filter_lvls &= ~BF_MASK(NRF_LOG_LEVEL_BITS, (NRF_LOG_LEVEL_BITS * backend_id));
        p_filter->filter_lvls |= BF_VAL(severity, NRF_LOG_LEVEL_BITS, (NRF_LOG_LEVEL_BITS * backend_id));

        p_module_data->filter = higher_lvl_get(p_filter->filter_lvls);
    }
}

static nrf_log_severity_t nrf_log_module_init_filter_get(uint32_t module_id)
{
    nrf_log_module_const_data_t * p_module_data =
                                        NRF_LOG_CONST_SECTION_VARS_GET(module_id);
    return NRF_LOG_FILTERS_ENABLED ? p_module_data->initial_lvl : p_module_data->compiled_lvl;
}

nrf_log_severity_t nrf_log_module_filter_get(uint32_t backend_id,
                                             uint32_t module_id,
                                             bool ordered_idx,
                                             bool dynamic)
{
    nrf_log_severity_t severity = NRF_LOG_SEVERITY_NONE;
    if (NRF_LOG_FILTERS_ENABLED && dynamic)
    {
        if (module_idx_get(&module_id, ordered_idx) == NRF_SUCCESS)
        {
            nrf_log_module_filter_data_t * p_filter = NRF_LOG_FILTER_SECTION_VARS_GET(module_id);
            severity = (nrf_log_severity_t)BF_GET(p_filter->filter_lvls,
                                                  NRF_LOG_LEVEL_BITS,
                                                  (backend_id*NRF_LOG_LEVEL_BITS));
        }
    }
    else if (!dynamic)
    {
        if (module_idx_get(&module_id, ordered_idx) == NRF_SUCCESS)
        {
            nrf_log_module_const_data_t * p_module_data =
                                                NRF_LOG_CONST_SECTION_VARS_GET(module_id);
            severity = (nrf_log_severity_t)p_module_data->compiled_lvl;
        }
    }
    return severity;
}
/**
 * Function examines current header and omits packets which are in progress.
 */
static bool invalid_packets_omit(nrf_log_header_t const * p_header, uint32_t * p_rd_idx)
{
    bool ret = false;
    if (p_header->base.generic.in_progress == 1)
    {
        switch (p_header->base.generic.type)
        {
        case HEADER_TYPE_STD:
            *p_rd_idx += (HEADER_SIZE + p_header->base.std.nargs);
            break;
        case HEADER_TYPE_HEXDUMP:
            *p_rd_idx += (HEADER_SIZE + p_header->base.hexdump.len);
            break;
        default:
            break;
        }
        ret = true;
    }
    return ret;
}
/**
 * @brief Skips the oldest, not processed logs to make space for new logs.
 * @details This function moves forward read index to prepare space for new logs.
 */

static uint32_t log_skip(void)
{
    uint16_t dropped = 0;

    (void)nrf_atomic_flag_set(&m_log_data.log_skipped);
    (void)nrf_atomic_flag_set(&m_log_data.log_skipping);

    uint32_t           rd_idx = m_log_data.rd_idx;
    uint32_t           mask   = m_buffer_mask;
    nrf_log_header_t * p_header = (nrf_log_header_t *)&m_log_data.buffer[rd_idx & mask];
    nrf_log_header_t   header;

    // Skip packets that may be invalid (interrupted while being in progress)
    do {
        if (invalid_packets_omit(p_header, &rd_idx))
        {
            //something was omitted. Point to new header and try again.
            p_header = (nrf_log_header_t *)&m_log_data.buffer[rd_idx & mask];
        }
        else
        {
            break;
        }
    } while (true);

    uint32_t i;
    for (i = 0; i < HEADER_SIZE; i++)
    {
        ((uint32_t*)&header)[i] = m_log_data.buffer[rd_idx++ & mask];
    }

    switch (header.base.generic.type)
    {
        case HEADER_TYPE_HEXDUMP:
            dropped = header.dropped;
            rd_idx += CEIL_DIV(header.base.hexdump.len, sizeof(uint32_t));
            break;
        case HEADER_TYPE_STD:
            dropped = header.dropped;
            rd_idx += header.base.std.nargs;
            break;
        default:
            ASSERT(false);
            break;
    }

    uint32_t log_skipping_tmp = nrf_atomic_flag_clear_fetch(&m_log_data.log_skipping);
    //update read index only if log_skip was not interrupted by another log skip
    if (log_skipping_tmp)
    {
        m_log_data.rd_idx = rd_idx;
    }

    return (uint32_t)dropped;
}

/**
 * @brief Function for getting number of dropped logs. Dropped counter is reset after reading.
 *
 * @return Number of dropped logs saturated to 16 bits.
 */
static inline uint32_t dropped_sat16_get(void)
{
    uint32_t dropped = nrf_atomic_u32_fetch_store(&m_log_data.log_dropped_cnt, 0);
    return __USAT(dropped, 16); //Saturate to 16 bits
}


static inline void std_header_set(uint32_t severity_mid,
                                      char const * const p_str,
                                      uint32_t nargs,
                                      uint32_t wr_idx,
                                      uint32_t mask)
{


    //Prepare header - in reverse order to ensure that packet type is validated (set to STD as last action)
    uint32_t module_id = severity_mid >> NRF_LOG_MODULE_ID_POS;
    uint32_t dropped   = dropped_sat16_get();
    ASSERT(module_id < nrf_log_module_cnt_get());
    m_log_data.buffer[(wr_idx + 1) & mask] = module_id | (dropped << 16);

    if (NRF_LOG_USES_TIMESTAMP)
    {
        m_log_data.buffer[(wr_idx + 2) & mask] = m_log_data.timestamp_func();
    }

    nrf_log_header_t * p_header    = (nrf_log_header_t *)&m_log_data.buffer[wr_idx & mask];
    p_header->base.std.severity    = severity_mid & NRF_LOG_LEVEL_MASK;
    p_header->base.std.nargs       = nargs;
    p_header->base.std.addr        = ((uint32_t)(p_str) & STD_ADDR_MASK);
    p_header->base.std.type        = HEADER_TYPE_STD;
    p_header->base.std.in_progress = 0;
}

#if NRF_LOG_DEFERRED
/**
 * When using RTOS, application can overide this function so that it could unblock 
 * the task that is responsible for flushing the logs.
 */
__WEAK void log_pending_hook( void )
{
}
#endif

/**
 * @brief Allocates chunk in a buffer for one entry and injects overflow if
 * there is no room for requested entry.
 *
 * @param content_len   Number of 32bit arguments. In case of allocating for hex dump it
 *                      is the size of the buffer in 32bit words (ceiled).
 * @param p_wr_idx      Pointer to write index.
 *
 * @return True if successful allocation, false otherwise.
 *
 */
static inline bool buf_prealloc(uint32_t content_len, uint32_t * p_wr_idx, bool std)
{
    uint32_t req_len = content_len + HEADER_SIZE;
    bool     ret            = true;
    CRITICAL_REGION_ENTER();
    *p_wr_idx = m_log_data.wr_idx;
    uint32_t available_words = (m_buffer_mask + 1) - (m_log_data.wr_idx - m_log_data.rd_idx);
    while (req_len > available_words)
    {
        UNUSED_RETURN_VALUE(nrf_atomic_u32_add(&m_log_data.log_dropped_cnt, 1));
        if (NRF_LOG_ALLOW_OVERFLOW)
        {
            uint32_t dropped_in_skip = log_skip();
            UNUSED_RETURN_VALUE(nrf_atomic_u32_add(&m_log_data.log_dropped_cnt, dropped_in_skip));
            available_words = (m_buffer_mask + 1) - (m_log_data.wr_idx - m_log_data.rd_idx);
        }
        else
        {
            ret = false;
            break;
        }
    }

    if (ret)
    {
        nrf_log_main_header_t invalid_header;
        invalid_header.raw = 0;

        if (std)
        {
            invalid_header.std.type        = HEADER_TYPE_STD;
            invalid_header.std.in_progress = 1;
            invalid_header.std.nargs       = content_len;
        }
        else
        {
            invalid_header.hexdump.type = HEADER_TYPE_HEXDUMP;
            invalid_header.hexdump.in_progress = 1;
            invalid_header.hexdump.len = content_len;
        }

        nrf_log_main_header_t * p_header =
                   (nrf_log_main_header_t *)&m_log_data.buffer[m_log_data.wr_idx & m_buffer_mask];

        p_header->raw = invalid_header.raw;

        m_log_data.wr_idx += req_len;
    }

    CRITICAL_REGION_EXIT();
    return ret;
}

char const * nrf_log_push(char * const p_str)
{
    if ((m_log_data.autoflush) || (p_str == NULL))
    {
        return p_str;
    }

    size_t ssize = strlen(p_str) + 1; // + 1 for null termination
    uint8_t * p_dst;
    // Allocate space in the ring buffer. It may be smaller than the requested string in case of buffer wrapping or when the ring buffer size is too small.
    // Once the string is copied into the buffer, the space is immediately freed. The string is kept in the buffer but can be overwritten.
    // It is done that way because there is no other place where space could be freed since string processing happens in
    // the logger backends, often by modules which are generic and not aware of internals of the logger.
    if (nrf_ringbuf_alloc(&m_log_push_ringbuf, &p_dst, &ssize, true) == NRF_SUCCESS)
    {
        ret_code_t err;

        memcpy(p_dst, p_str, ssize);

        //Terminate in case string was partial.
        p_dst[ssize - 1] = '\0';

        err = nrf_ringbuf_put(&m_log_push_ringbuf, ssize);
        ASSERT(err == NRF_SUCCESS);

        //Immediately free the space where string was put.
        err = nrf_ringbuf_free(&m_log_push_ringbuf, ssize);
        ASSERT(err == NRF_SUCCESS);

        return (char const *)p_dst;
    }
    else
    {
        return NULL;
    }
}

static inline void std_n(uint32_t           severity_mid,
                         char const * const p_str,
                         uint32_t const *   args,
                         uint32_t           nargs)
{
    uint32_t mask   = m_buffer_mask;
    uint32_t wr_idx;

    if (buf_prealloc(nargs, &wr_idx, true))
    {
        // Proceed only if buffer was successfully preallocated.

        uint32_t data_idx = wr_idx + HEADER_SIZE;
        uint32_t i;
        for (i = 0; i < nargs; i++)
        {
            m_log_data.buffer[data_idx++ & mask] =args[i];
        }
        std_header_set(severity_mid, p_str, nargs, wr_idx, mask);
    }

    if (m_log_data.autoflush)
    {
#if NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED
        CRITICAL_REGION_ENTER();
#endif // NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED

        NRF_LOG_FLUSH();

#if NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED
        CRITICAL_REGION_EXIT();
#endif // NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED
    }

#if (NRF_LOG_DEFERRED == 1)
    log_pending_hook();
#endif

}

void nrf_log_frontend_std_0(uint32_t severity_mid, char const * const p_str)
{
    std_n(severity_mid, p_str, NULL, 0);
}


void nrf_log_frontend_std_1(uint32_t            severity_mid,
                            char const * const p_str,
                            uint32_t           val0)
{
    uint32_t args[] = {val0};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}


void nrf_log_frontend_std_2(uint32_t           severity_mid,
                            char const * const p_str,
                            uint32_t           val0,
                            uint32_t           val1)
{
    uint32_t args[] = {val0, val1};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}


void nrf_log_frontend_std_3(uint32_t           severity_mid,
                            char const * const p_str,
                            uint32_t           val0,
                            uint32_t           val1,
                            uint32_t           val2)
{
    uint32_t args[] = {val0, val1, val2};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}


void nrf_log_frontend_std_4(uint32_t           severity_mid,
                            char const * const p_str,
                            uint32_t           val0,
                            uint32_t           val1,
                            uint32_t           val2,
                            uint32_t           val3)
{
    uint32_t args[] = {val0, val1, val2, val3};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}


void nrf_log_frontend_std_5(uint32_t           severity_mid,
                            char const * const p_str,
                            uint32_t           val0,
                            uint32_t           val1,
                            uint32_t           val2,
                            uint32_t           val3,
                            uint32_t           val4)
{
    uint32_t args[] = {val0, val1, val2, val3, val4};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}


void nrf_log_frontend_std_6(uint32_t           severity_mid,
                            char const * const p_str,
                            uint32_t           val0,
                            uint32_t           val1,
                            uint32_t           val2,
                            uint32_t           val3,
                            uint32_t           val4,
                            uint32_t           val5)
{
    uint32_t args[] = {val0, val1, val2, val3, val4, val5};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}


void nrf_log_frontend_hexdump(uint32_t           severity_mid,
                              const void * const p_data,
                              uint16_t           length)
{
    uint32_t mask   = m_buffer_mask;

    uint32_t wr_idx;
    if (buf_prealloc(CEIL_DIV(length, sizeof(uint32_t)), &wr_idx, false))
    {
        uint32_t header_wr_idx = wr_idx;
        wr_idx += HEADER_SIZE;

        uint32_t space0 = sizeof(uint32_t) * (m_buffer_mask + 1 - (wr_idx & mask));
        if (length <= space0)
        {
            memcpy(&m_log_data.buffer[wr_idx & mask], p_data, length);
        }
        else
        {
            memcpy(&m_log_data.buffer[wr_idx & mask], p_data, space0);
            memcpy(&m_log_data.buffer[0], &((uint8_t *)p_data)[space0], length - space0);
        }

        //Prepare header - in reverse order to ensure that packet type is validated (set to HEXDUMP as last action)
        if (NRF_LOG_USES_TIMESTAMP)
        {
           m_log_data.buffer[(header_wr_idx + 2) & mask] = m_log_data.timestamp_func();
        }

        uint32_t module_id = severity_mid >> NRF_LOG_MODULE_ID_POS;
        uint32_t dropped   = dropped_sat16_get();
        m_log_data.buffer[(header_wr_idx + 1) & mask] = module_id | (dropped << 16);
        //Header prepare
        nrf_log_header_t * p_header = (nrf_log_header_t *)&m_log_data.buffer[header_wr_idx & mask];
        p_header->base.hexdump.severity    = severity_mid & NRF_LOG_LEVEL_MASK;
        p_header->base.hexdump.offset      = 0;
        p_header->base.hexdump.len         = length;
        p_header->base.hexdump.type        = HEADER_TYPE_HEXDUMP;
        p_header->base.hexdump.in_progress = 0;
    }

    if (m_log_data.autoflush)
    {
#if NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED
        CRITICAL_REGION_ENTER();
#endif // NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED

        NRF_LOG_FLUSH();

#if NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED
        CRITICAL_REGION_EXIT();
#endif // NRF_LOG_NON_DEFFERED_CRITICAL_REGION_ENABLED
    }

#if (NRF_LOG_DEFERRED == 1)
    log_pending_hook();
#endif
}


bool buffer_is_empty(void)
{
    return (m_log_data.rd_idx == m_log_data.wr_idx);
}

bool nrf_log_frontend_dequeue(void)
{

    if (buffer_is_empty())
    {
        return false;
    }
    m_log_data.log_skipped      = 0;
    //It has to be ensured that reading rd_idx occurs after skipped flag is cleared.
    __DSB();
    uint32_t           rd_idx   = m_log_data.rd_idx;
    uint32_t           mask     = m_buffer_mask;
    nrf_log_header_t * p_header = (nrf_log_header_t *)&m_log_data.buffer[rd_idx & mask];
    nrf_log_header_t   header;
    nrf_memobj_t *     p_msg_buf = NULL;
    size_t             memobj_offset = 0;
    uint32_t           severity = 0;

    // Skip any in progress packets.
    do {
        if (invalid_packets_omit(p_header, &rd_idx) && (m_log_data.log_skipped == 0))
        {
            //Check if end of data is not reached.
            if (rd_idx >= m_log_data.wr_idx)
            {
                m_log_data.rd_idx     = m_log_data.wr_idx;
                return false;
            }
            //something was omitted. Point to new header and try again.
            p_header = (nrf_log_header_t *)&m_log_data.buffer[rd_idx & mask];
        }
        else
        {
            break;
        }
    } while (true);

    uint32_t i;
    for (i = 0; i < HEADER_SIZE; i++)
    {
        ((uint32_t*)&header)[i] = m_log_data.buffer[rd_idx++ & mask];
    }

    if (header.base.generic.type == HEADER_TYPE_HEXDUMP)
    {
        uint32_t orig_data_len  = header.base.hexdump.len;
        uint32_t data_len       = MIN(header.base.hexdump.len, NRF_LOG_MAX_HEXDUMP); //limit the data
        header.base.hexdump.len = data_len;
        uint32_t msg_buf_size8  = sizeof(uint32_t)*HEADER_SIZE + data_len;
        severity = header.base.hexdump.severity;
        p_msg_buf = nrf_memobj_alloc(&log_mempool, msg_buf_size8);

        if (p_msg_buf)
        {
            nrf_memobj_get(p_msg_buf);
            nrf_memobj_write(p_msg_buf, &header, HEADER_SIZE*sizeof(uint32_t), memobj_offset);
            memobj_offset += HEADER_SIZE*sizeof(uint32_t);

            uint32_t space0 = sizeof(uint32_t) * (mask + 1 - (rd_idx & mask));
            if (data_len > space0)
                    {
                uint8_t * ptr0 = space0 ?
                                 (uint8_t *)&m_log_data.buffer[rd_idx & mask] :
                                 (uint8_t *)&m_log_data.buffer[0];
                uint8_t   len0 = space0 ? space0 : data_len;
                uint8_t * ptr1 = space0 ?
                                 (uint8_t *)&m_log_data.buffer[0] : NULL;
                uint8_t len1 = space0 ? data_len - space0 : 0;

                nrf_memobj_write(p_msg_buf, ptr0, len0, memobj_offset);
                memobj_offset += len0;
                if (ptr1)
                {
                    nrf_memobj_write(p_msg_buf, ptr1, len1, memobj_offset);
                }
            }
            else
            {
                uint8_t * p_data = (uint8_t *)&m_log_data.buffer[rd_idx & mask];
                nrf_memobj_write(p_msg_buf, p_data, data_len, memobj_offset);
            }
            rd_idx += CEIL_DIV(orig_data_len, 4);
        }
    }
    else if (header.base.generic.type == HEADER_TYPE_STD) // standard entry
    {
        header.base.std.nargs = MIN(header.base.std.nargs, NRF_LOG_MAX_NUM_OF_ARGS);
        uint32_t msg_buf_size32 = HEADER_SIZE + header.base.std.nargs;
        severity = header.base.std.severity;

        p_msg_buf = nrf_memobj_alloc(&log_mempool, msg_buf_size32*sizeof(uint32_t));

        if (p_msg_buf)
        {
            nrf_memobj_get(p_msg_buf);
            nrf_memobj_write(p_msg_buf, &header, HEADER_SIZE*sizeof(uint32_t), memobj_offset);
            memobj_offset += HEADER_SIZE*sizeof(uint32_t);

            for (i = 0; i < header.base.std.nargs; i++)
            {
                nrf_memobj_write(p_msg_buf, &m_log_data.buffer[rd_idx++ & mask],
                                 sizeof(uint32_t), memobj_offset);
                memobj_offset += sizeof(uint32_t);
            }
        }
    }
    else
    {
        //Do nothing. In case of log overflow buffer can contain corrupted data.
    }

    if (p_msg_buf)
    {
        nrf_log_backend_t const * p_backend = m_log_data.p_backend_head;
        if (NRF_LOG_ALLOW_OVERFLOW && m_log_data.log_skipped)
        {
            // Check if any log was skipped during log processing. Do not forward log if skipping 
            // occured because data may be invalid.
            nrf_memobj_put(p_msg_buf);
        }
        else
        {
            while (p_backend)
            {
                bool entry_accepted = false;
                if (nrf_log_backend_is_enabled(p_backend) == true)
                {
                    if (NRF_LOG_FILTERS_ENABLED)
                    {
                        uint8_t backend_id = nrf_log_backend_id_get(p_backend);
                        nrf_log_module_filter_data_t * p_module_filter =
                                                 NRF_LOG_FILTER_SECTION_VARS_GET(header.module_id);
                        uint32_t backend_lvl = BF_GET(p_module_filter->filter_lvls,
                                                      NRF_LOG_LEVEL_BITS,
                                                      (backend_id*NRF_LOG_LEVEL_BITS));

                        //Degrade INFO_RAW level to INFO.
                        severity = (severity == NRF_LOG_SEVERITY_INFO_RAW) ?
                                                                 NRF_LOG_SEVERITY_INFO : severity;
                        if (backend_lvl >= severity)
                        {
                            entry_accepted = true;
                        }
                    }
                    else
                    {
                        (void)severity;
                        entry_accepted = true;
                    }
                }
                if (entry_accepted)
                {
                    nrf_log_backend_put(p_backend, p_msg_buf);
                }
                p_backend = p_backend->p_cb->p_next;
            }

            nrf_memobj_put(p_msg_buf);

            if (NRF_LOG_ALLOW_OVERFLOW)
            {
                // Read index can be moved forward only if dequeueing process was not interrupt by
                // skipping procedure. If NRF_LOG_ALLOW_OVERFLOW is set then in case of buffer gets full
                // and new logger entry occurs, oldest entry is removed. In that case read index is
                // changed and updating it here would corrupt the internal circular buffer.
                CRITICAL_REGION_ENTER();
                if (m_log_data.log_skipped == 0)
                {
                    m_log_data.rd_idx = rd_idx;
                }
                CRITICAL_REGION_EXIT();
            }
            else
            {
                m_log_data.rd_idx = rd_idx;
            }
        }
    }
    else
    {
        //Could not allocate memobj - backends are not freeing them on time.
        nrf_log_backend_t const * p_backend = m_log_data.p_backend_head;
        //Flush all backends
        while (p_backend)
        {
            nrf_log_backend_flush(p_backend);
            p_backend = p_backend->p_cb->p_next;
        }
        NRF_LOG_WARNING("Backends flushed");
    }

    return buffer_is_empty() ? false : true;
}

static int32_t backend_id_assign(void)
{
    int32_t candidate_id;
    nrf_log_backend_t const * p_backend;
    bool id_available;
    for (candidate_id = 0; candidate_id < NRF_LOG_MAX_BACKENDS; candidate_id++)
    {
        p_backend = m_log_data.p_backend_head;
        id_available = true;
        while (p_backend)
        {
            if (nrf_log_backend_id_get(p_backend) == candidate_id)
            {
                id_available = false;
                break;
            }
            p_backend = p_backend->p_cb->p_next;
        }
        if (id_available)
        {
            return candidate_id;
        }
    }
    return -1;
}

int32_t nrf_log_backend_add(nrf_log_backend_t const * p_backend, nrf_log_severity_t severity)
{
    int32_t id = backend_id_assign();
    if (id == -1)
    {
        return id;
    }

    nrf_log_backend_id_set(p_backend, id);
    //add to list
    if (m_log_data.p_backend_head == NULL)
    {
       m_log_data.p_backend_head   = p_backend;
       p_backend->p_cb->p_next = NULL;
    }
    else
    {
        p_backend->p_cb->p_next = m_log_data.p_backend_head->p_cb->p_next;
        m_log_data.p_backend_head->p_cb->p_next = p_backend;
    }

    if (NRF_LOG_FILTERS_ENABLED)
    {
        uint32_t i;
        for (i = 0; i < nrf_log_module_cnt_get(); i++)
        {
            nrf_log_severity_t buildin_lvl = nrf_log_module_init_filter_get(i);
            nrf_log_severity_t actual_severity = MIN(buildin_lvl, severity);
            nrf_log_module_filter_set(nrf_log_backend_id_get(p_backend), i, actual_severity);
        }
    }

    return id;
}

void nrf_log_backend_remove(nrf_log_backend_t const * p_backend)
{
    nrf_log_backend_t const * p_curr = m_log_data.p_backend_head;
    nrf_log_backend_t const * p_prev = NULL;
    while (p_curr != p_backend)
    {
        p_prev = p_curr;
        p_curr = p_curr->p_cb->p_next;
    }

    if (p_prev)
    {
        p_prev->p_cb->p_next = p_backend->p_cb->p_next;
    }
    else
    {
        m_log_data.p_backend_head = NULL;
    }

    p_backend->p_cb->id = NRF_LOG_BACKEND_INVALID_ID;
}

void nrf_log_panic(void)
{
    nrf_log_backend_t const * p_backend = m_log_data.p_backend_head;
    m_log_data.autoflush = true;
    while (p_backend)
    {
        nrf_log_backend_enable(p_backend);
        nrf_log_backend_panic_set(p_backend);
        p_backend = p_backend->p_cb->p_next;
    }
}

#if NRF_MODULE_ENABLED(LOG_CONFIG_LOAD_STORE)
#include "fds.h"
#define LOG_CONFIG_FILE_ID   0x106E
#define LOG_CONFIG_RECORD_ID 0x3427

ret_code_t nrf_log_config_store(void)
{
    fds_record_desc_t desc = {0};
    fds_find_token_t  token = {0};
    fds_record_t      record = {
            .file_id = LOG_CONFIG_FILE_ID,
            .key     = LOG_CONFIG_RECORD_ID,
            .data = {
                    .p_data       = NRF_LOG_FILTER_SECTION_VARS_GET(0),
                    .length_words = NRF_SECTION_LENGTH(log_filter_data)/sizeof(uint32_t)
            }
    };
    ret_code_t ret = fds_record_find(LOG_CONFIG_FILE_ID, LOG_CONFIG_RECORD_ID, &desc, &token);
    if (ret == NRF_SUCCESS)
    {
        ret = fds_record_update(&desc, &record);
        NRF_LOG_INFO("Logger configuration file updated with result:%d", ret);
    }
    else if (ret == FDS_ERR_NOT_FOUND)
    {
        ret = fds_record_write(&desc, &record);
        NRF_LOG_INFO("Logger configuration file written with result:%d", ret);
    }
    else
    {
        ret = NRF_ERROR_INTERNAL;
    }
    return ret;
}

ret_code_t nrf_log_config_load(void)
{
    fds_record_desc_t desc = {0};
    fds_find_token_t  token = {0};

    ret_code_t ret = fds_record_find(LOG_CONFIG_FILE_ID, LOG_CONFIG_RECORD_ID, &desc, &token);
    if (ret == NRF_SUCCESS)
    {
        fds_flash_record_t record = {0};
        ret = fds_record_open(&desc, &record);
        if (ret == NRF_SUCCESS)
        {
            void * p_dest = (void *)NRF_LOG_FILTER_SECTION_VARS_GET(0);
            uint32_t length   = NRF_SECTION_LENGTH(log_filter_data);
            memcpy(p_dest, record.p_data, length);
            ret = fds_record_close(&desc);
        }
    }
    else if (ret == FDS_ERR_NOT_FOUND)
    {
        NRF_LOG_WARNING("Logger configuration file not found.");
        ret = NRF_ERROR_NOT_FOUND;
    }
    else
    {
        ret = NRF_ERROR_INTERNAL;
    }

    return ret;
}
#endif //LOG_CONFIG_LOAD_STORE_ENABLED

#if NRF_LOG_CLI_CMDS && NRF_CLI_ENABLED
#include "nrf_cli.h"

typedef void (*nrf_log_cli_backend_cmd_t)(nrf_cli_t const *         p_cli,
                                          nrf_log_backend_t const * p_backend,
                                          size_t                    argc,
                                          char * *                  argv);

static const char * m_severity_lvls[] = {
        "none",
        "error",
        "warning",
        "info",
        "debug",
};

static const char * m_severity_lvls_sorted[] = {
        "debug",
        "error",
        "info",
        "none",
        "warning",
};

/**
 * @brief Function for finding backend instance with given name.
 *
 * @param p_name Name of the backend instance.
 *
 * @return Pointer to the instance or NULL.
 *
 */
static nrf_log_backend_t const * backend_find(char const * p_name)
{
    size_t num_of_backends;
    nrf_log_backend_t const * p_backend;

    num_of_backends = NRF_SECTION_ITEM_COUNT(NRF_LOG_BACKEND_SECTION_NAME, nrf_log_backend_t);
    for (size_t i = 0; i < num_of_backends; i++)
    {
        p_backend = NRF_SECTION_ITEM_GET(NRF_LOG_BACKEND_SECTION_NAME, nrf_log_backend_t, i);
        if (strcmp(p_name, p_backend->p_name) == 0)
        {
            return p_backend;
        }
    }
    return NULL;
}

/**
 * @brief Function for executing command on given backend.
 */
static void nrf_cli_backend_cmd_execute(nrf_cli_t const *         p_cli,
                                        size_t                    argc,
                                        char * *                  argv,
                                        nrf_log_cli_backend_cmd_t func)
{
    //Based on the structure of backend commands, name of the backend can be found at -1 (log backend <name> command).
    char const * p_backend_name = argv[-1];

    nrf_log_backend_t const * p_backend = backend_find(p_backend_name);

    if (p_backend)
    {
        func(p_cli, p_backend, argc, argv);
    }
    else
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Invalid backend: %s\r\n", p_backend_name);
    }
}


static void log_status(nrf_cli_t const *         p_cli,
                       nrf_log_backend_t const * p_backend,
                       size_t                    argc,
                       char * *                  argv)
{
    UNUSED_PARAMETER(argc);
    UNUSED_PARAMETER(argv);

    uint32_t modules_cnt = nrf_log_module_cnt_get();
    uint32_t i;


    if (!nrf_log_backend_is_enabled(p_backend))
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Logs are halted!\r\n");
    }
    nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "%-40s | current | built-in \r\n", "module_name");
    nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "----------------------------------------------------------\r\n");
    for (i = 0; i < modules_cnt; i++)
    {
        uint32_t backend_id = p_backend->p_cb->id;
        nrf_log_severity_t module_dynamic_lvl =
                              nrf_log_module_filter_get(backend_id, i, true, true);
        nrf_log_severity_t module_compiled_lvl =
                              nrf_log_module_filter_get(backend_id, i, true, false);
        nrf_log_severity_t actual_compiled_lvl =
                              MIN(module_compiled_lvl, (nrf_log_severity_t)NRF_LOG_DEFAULT_LEVEL);

        nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "%-40s | %-7s | %s%s\r\n",
                                  nrf_log_module_name_get(i, true),
                                  m_severity_lvls[module_dynamic_lvl],
                                  m_severity_lvls[actual_compiled_lvl],
                                  actual_compiled_lvl < module_compiled_lvl ? "*" : "");
    }
}


static void log_self_status(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    log_status(p_cli, p_cli->p_log_backend, argc, argv);
}


static void log_backend_status(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    nrf_cli_backend_cmd_execute(p_cli, argc, argv, log_status);
}


static bool module_id_get(const char * p_name, uint32_t * p_id)
{
    uint32_t modules_cnt = nrf_log_module_cnt_get();
    const char * p_tmp_name;
    uint32_t j;
    for (j = 0; j < modules_cnt; j++)
    {
        p_tmp_name = nrf_log_module_name_get(j, false);
        if (strncmp(p_tmp_name, p_name, 32) == 0)
        {
            *p_id = j;
            break;
        }
    }
    return (j != modules_cnt);
}


static bool module_id_filter_set(uint32_t backend_id,
                                 uint32_t module_id,
                                 nrf_log_severity_t lvl)
{
    nrf_log_severity_t buildin_lvl = nrf_log_module_filter_get(backend_id, module_id, false, false);
    if (lvl > buildin_lvl)
    {
        return false;
    }
    else
    {
        nrf_log_module_filter_set(backend_id, module_id, lvl);
        return true;
    }
}


static void log_ctrl(nrf_cli_t const *         p_cli,
                     nrf_log_backend_t const * p_backend,
                     size_t                    argc,
                     char * *                  argv)
{
    nrf_log_severity_t lvl;
    uint32_t first_m_name_idx;
    uint32_t i;
    bool     all_modules = false;
    uint32_t backend_id = p_backend->p_cb->id;

    if (argc >  0)
    {
        if (strncmp(argv[0], "enable", 7) == 0)
        {
            if (argc == 1)
            {
                nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Bad parameter count.\r\n");
                return;
            }

            if (argc == 2)
            {
                all_modules = true;
            }

            for (i = 0; i < ARRAY_SIZE(m_severity_lvls); i++)
            {
                if (strncmp(argv[1], m_severity_lvls[i], 10) == 0)
                {
                    break;
                }
            }

            if (i == ARRAY_SIZE(m_severity_lvls))
            {
                nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Unknown severity level: %s\r\n", argv[1]);
                return;
            }

            lvl = (nrf_log_severity_t)i;
            first_m_name_idx = 2;

        }
        else if (strncmp(argv[0], "disable", 8) == 0)
        {
            if (argc == 1)
            {
                all_modules = true;
            }
            lvl = NRF_LOG_SEVERITY_NONE;
            first_m_name_idx = 1;
        }
        else
        {
            nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Unknown option: %s\r\n", argv[0]);
            return;
        }

        if (all_modules)
        {
            for (i = 0; i < nrf_log_module_cnt_get(); i++)
            {
                if (module_id_filter_set(backend_id, i, lvl) == false)
                {
                    nrf_cli_fprintf(p_cli, NRF_CLI_ERROR,
                                    "Level unavailable for module: %s\r\n",
                                    nrf_log_module_name_get(i, false));
                }
            }
        }
        else
        {
            for (i = first_m_name_idx; i < argc; i++)
            {
                uint32_t module_id = 0;
                if (module_id_get(argv[i], &module_id) == false)
                {
                    nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Unknown module:%s\r\n", argv[i]);
                }
                else if (module_id_filter_set(backend_id, module_id, lvl) == false)
                {
                    nrf_cli_fprintf(p_cli, NRF_CLI_ERROR,
                                    "Level unavailable for module: %s\r\n",
                                    nrf_log_module_name_get(module_id, false));
                }
                else
                {
                    /* empty */
                }
            }
        }
    }
}


static void log_self_ctrl(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    log_ctrl(p_cli, p_cli->p_log_backend, argc, argv);
}


static void log_backend_ctrl(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    nrf_cli_backend_cmd_execute(p_cli, argc, argv, log_ctrl);
}

static void module_name_get(size_t idx, nrf_cli_static_entry_t * p_static);

NRF_CLI_CREATE_DYNAMIC_CMD(m_module_name, module_name_get);

static void module_name_get(size_t idx, nrf_cli_static_entry_t * p_static)
{
    p_static->handler = NULL;
    p_static->p_help  = NULL;
    p_static->p_subcmd = &m_module_name;
    p_static->p_syntax = nrf_log_module_name_get(idx, true);
}


static void severity_lvl_get(size_t idx, nrf_cli_static_entry_t * p_static)
{
    p_static->handler = NULL;
    p_static->p_help  = NULL;
    p_static->p_subcmd = &m_module_name;
    p_static->p_syntax = (idx < ARRAY_SIZE(m_severity_lvls_sorted)) ?
                                                    m_severity_lvls_sorted[idx] : NULL;
}

NRF_CLI_CREATE_DYNAMIC_CMD(m_severity_lvl, severity_lvl_get);


static void log_halt(nrf_cli_t const *         p_cli,
                     nrf_log_backend_t const * p_backend,
                     size_t                    argc,
                     char * *                  argv)
{
    UNUSED_PARAMETER(argc);
    UNUSED_PARAMETER(argv);

    nrf_log_backend_disable(p_backend);
}


static void log_backend_halt(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    nrf_cli_backend_cmd_execute(p_cli, argc, argv, log_halt);
}


static void log_self_halt(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    log_halt(p_cli, p_cli->p_log_backend, argc, argv);
}


static void log_go(nrf_cli_t const *         p_cli,
                   nrf_log_backend_t const * p_backend,
                   size_t                    argc,
                   char * *                  argv)
{
    UNUSED_PARAMETER(argc);
    UNUSED_PARAMETER(argv);

    nrf_log_backend_enable(p_backend);
}


static void log_backend_go(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    nrf_cli_backend_cmd_execute(p_cli, argc, argv, log_go);
}


static void log_self_go(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    log_go(p_cli, p_cli->p_log_backend, argc, argv);
}


static void log_cmd_backends_list(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
        return;
    }
    size_t num_of_backends;

    num_of_backends = NRF_SECTION_ITEM_COUNT(NRF_LOG_BACKEND_SECTION_NAME, nrf_log_backend_t);
    for (size_t i = 0; i < num_of_backends; i++)
    {
        nrf_log_backend_t const * p_backend =
                           NRF_SECTION_ITEM_GET(NRF_LOG_BACKEND_SECTION_NAME, nrf_log_backend_t, i);

        if (p_backend->p_cb->id == NRF_LOG_BACKEND_INVALID_ID)
        {
            nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL,
                            "%s\r\n"
                            "\t- Status: deactivated\r\n\r\n",
                            p_backend->p_name);
        }
        else
        {
            nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL,
                            "%s\r\n"
                            "\t- Status: %s\r\n"
                            "\t- ID: %d\r\n\r\n",
                            p_backend->p_name,
                            p_backend->p_cb->enabled ? "enabled" : "disabled",
                            p_backend->p_cb->id);
        }
    }
}


NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_log_backend)
{
    NRF_CLI_CMD(disable, &m_module_name,
        "'log disable <module_0> .. <module_n>' disables logs in specified "
        "modules (all if no modules specified).",
        log_backend_ctrl),
    NRF_CLI_CMD(enable, &m_severity_lvl,
        "'log enable <level> <module_0> ...  <module_n>' enables logs up to given level in "
        "specified modules (all if no modules specified).",
        log_backend_ctrl),
    NRF_CLI_CMD(go, NULL, "Resume logging", log_backend_go),
    NRF_CLI_CMD(halt, NULL, "Halt logging", log_backend_halt),
    NRF_CLI_CMD(status, NULL, "Logger status", log_backend_status),
    NRF_CLI_SUBCMD_SET_END
};

static void backend_name_get(size_t idx, nrf_cli_static_entry_t * p_static)
{
    p_static->handler = NULL;
    p_static->p_help  = NULL;
    p_static->p_subcmd = &m_sub_log_backend;
    p_static->p_syntax  = NULL;

    nrf_log_backend_t const * p_backend;
    size_t                    active_idx = 0;
    uint32_t                  i;

    for (i = 0; i < NRF_SECTION_ITEM_COUNT(NRF_LOG_BACKEND_SECTION_NAME, nrf_log_backend_t); i++)
    {
        p_backend = NRF_SECTION_ITEM_GET(NRF_LOG_BACKEND_SECTION_NAME, nrf_log_backend_t, i);
        if (p_backend->p_cb->id != NRF_LOG_BACKEND_INVALID_ID)
        {
            if (idx == active_idx)
            {
                p_static->p_syntax = p_backend->p_name;
                break;
            }
            else
            {
                active_idx++;
            }
        }
    }
}

NRF_CLI_CREATE_DYNAMIC_CMD(m_backend_name_dynamic, backend_name_get);

static void log_config_load_cmd(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    UNUSED_PARAMETER(argc);
    UNUSED_PARAMETER(argv);

    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
        return;
    }

#if LOG_CONFIG_LOAD_STORE_ENABLED
    if (nrf_log_config_load() == NRF_SUCCESS)
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "Configuration loaded.\r\n");
    }
    else
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Failed to load the configuration.\r\n");
    }
#else
    nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Not supported.\r\n");
#endif
}

static void log_config_store_cmd(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    UNUSED_PARAMETER(argc);
    UNUSED_PARAMETER(argv);

    if (nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
        return;
    }

#if LOG_CONFIG_LOAD_STORE_ENABLED
    if (nrf_log_config_store() == NRF_SUCCESS)
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "Configuration stored.\r\n");
    }
    else
    {
        nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Failed to store the configuration.\r\n");
    }
#else
    nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Not supported.\r\n");
#endif
}

NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_log_config)
{
    NRF_CLI_CMD(load, NULL, "Load configuration stored in non-volatile memory.", log_config_load_cmd),
    NRF_CLI_CMD(store, NULL, "Store current configuration in non-volatile memory.", log_config_store_cmd),
    NRF_CLI_SUBCMD_SET_END
};

NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_log_stat)
{
    NRF_CLI_CMD(backend, &m_backend_name_dynamic, "Logger backends commands.", NULL),
    NRF_CLI_CMD(config, &m_sub_log_config, "Manage logger configuration", NULL),
    NRF_CLI_CMD(disable, &m_module_name,
        "'log disable <module_0> .. <module_n>' disables logs in specified "
        "modules (all if no modules specified).",
        log_self_ctrl),
    NRF_CLI_CMD(enable, &m_severity_lvl,
        "'log enable <level> <module_0> ...  <module_n>' enables logs up to given level in "
        "specified modules (all if no modules specified).",
        log_self_ctrl),
    NRF_CLI_CMD(go, NULL, "Resume logging", log_self_go),
    NRF_CLI_CMD(halt, NULL, "Halt logging", log_self_halt),
    NRF_CLI_CMD(list_backends, NULL, "Lists logger backends.", log_cmd_backends_list),
    NRF_CLI_CMD(status, NULL, "Logger status", log_self_status),
    NRF_CLI_SUBCMD_SET_END
};

static void log_cmd(nrf_cli_t const * p_cli, size_t argc, char **argv)
{
    if ((argc == 1) || nrf_cli_help_requested(p_cli))
    {
        nrf_cli_help_print(p_cli, NULL, 0);
        return;
    }

    nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "%s:%s%s\r\n", argv[0], " unknown parameter: ", argv[1]);
}

NRF_CLI_CMD_REGISTER(log, &m_sub_log_stat, "Commands for controlling logger", log_cmd);

#endif //NRF_LOG_CLI_CMDS

#endif // NRF_MODULE_ENABLED(NRF_LOG)
