/**
 * Copyright (c) 2016 - 2018, 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 "nrf_log_str_formatter.h"
#include "nrf_log_internal.h"
#include "nrf_log_ctrl.h"
#include "nrf_fprintf.h"
#include <ctype.h>

#define NRF_LOG_COLOR_CODE_DEFAULT "\x1B[0m"
#define NRF_LOG_COLOR_CODE_BLACK   "\x1B[1;30m"
#define NRF_LOG_COLOR_CODE_RED     "\x1B[1;31m"
#define NRF_LOG_COLOR_CODE_GREEN   "\x1B[1;32m"
#define NRF_LOG_COLOR_CODE_YELLOW  "\x1B[1;33m"
#define NRF_LOG_COLOR_CODE_BLUE    "\x1B[1;34m"
#define NRF_LOG_COLOR_CODE_MAGENTA "\x1B[1;35m"
#define NRF_LOG_COLOR_CODE_CYAN    "\x1B[1;36m"
#define NRF_LOG_COLOR_CODE_WHITE   "\x1B[1;37m"

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

static const char * m_colors[] = {
        NRF_LOG_COLOR_CODE_DEFAULT,
        NRF_LOG_COLOR_CODE_BLACK,
        NRF_LOG_COLOR_CODE_RED,
        NRF_LOG_COLOR_CODE_GREEN,
        NRF_LOG_COLOR_CODE_YELLOW,
        NRF_LOG_COLOR_CODE_BLUE,
        NRF_LOG_COLOR_CODE_MAGENTA,
        NRF_LOG_COLOR_CODE_CYAN,
        NRF_LOG_COLOR_CODE_WHITE,
};

static uint32_t m_freq;
static uint32_t m_timestamp_div;

static void timestamp_print(nrf_fprintf_ctx_t * p_ctx, uint32_t timestamp)
{
    if (NRF_LOG_USES_TIMESTAMP)
    {
        if (NRF_LOG_STR_FORMATTER_TIMESTAMP_FORMAT_ENABLED)
        {
            timestamp        /= m_timestamp_div;
            uint32_t seconds  = timestamp/m_freq;
            uint32_t hours    = seconds/3600;
            seconds          -= hours * 3600;
            uint32_t mins     = seconds/60;
            seconds          -= mins * 60;

            uint32_t reminder = timestamp % m_freq;
            uint32_t ms       = (reminder * 1000)/m_freq;
            uint32_t us       = (1000*(1000*reminder - (ms * m_freq)))/m_freq;

            nrf_fprintf(p_ctx, "[%02d:%02d:%02d.%03d,%03d] ", hours, mins, seconds, ms, us);
        }
        else
        {
            nrf_fprintf(p_ctx, "[%08lu] ", timestamp);
        }
    }
}
static void prefix_process(nrf_log_str_formatter_entry_params_t * p_params,
                           nrf_fprintf_ctx_t * p_ctx)
{
    if (p_params->dropped)
    {
        nrf_fprintf(p_ctx,
                    "%sLogs dropped (%d)%s\r\n",
                    NRF_LOG_COLOR_CODE_RED,
                    p_params->dropped,
                    NRF_LOG_COLOR_CODE_DEFAULT);
    }

    if (!(p_params->severity == NRF_LOG_SEVERITY_INFO_RAW))
    {
        if (p_params->use_colors)
        {
            nrf_fprintf(p_ctx, "%s",
                          m_colors[nrf_log_color_id_get( p_params->module_id, p_params->severity)]);
        }
        timestamp_print(p_ctx, p_params->timestamp);

        nrf_fprintf(p_ctx, "<%s> %s: ",
           severity_names[p_params->severity], nrf_log_module_name_get(p_params->module_id, false));
    }
}

static void postfix_process(nrf_log_str_formatter_entry_params_t * p_params,
                            nrf_fprintf_ctx_t * p_ctx,
                            bool newline)
{
    if (!(p_params->severity == NRF_LOG_SEVERITY_INFO_RAW))
    {
        if (p_params->use_colors)
        {
            nrf_fprintf(p_ctx, "%s", m_colors[0]);
        }
        nrf_fprintf(p_ctx, "\r\n");
    }
    else if (newline)
    {
        nrf_fprintf(p_ctx, "\r\n");
    }
    nrf_fprintf_buffer_flush(p_ctx);
}

void nrf_log_std_entry_process(char const * p_str,
                                  uint32_t const * p_args,
                                  uint32_t nargs,
                                  nrf_log_str_formatter_entry_params_t * p_params,
                                  nrf_fprintf_ctx_t * p_ctx)
{
    bool auto_flush = p_ctx->auto_flush;
    p_ctx->auto_flush = false;

    prefix_process(p_params, p_ctx);

    switch (nargs)
    {
        case 0:
            nrf_fprintf(p_ctx, p_str);
            break;
        case 1:
            nrf_fprintf(p_ctx, p_str, p_args[0]);
            break;
        case 2:
            nrf_fprintf(p_ctx, p_str, p_args[0], p_args[1]);
            break;
        case 3:
            nrf_fprintf(p_ctx, p_str, p_args[0], p_args[1], p_args[2]);
            break;
        case 4:
            nrf_fprintf(p_ctx, p_str, p_args[0], p_args[1], p_args[2], p_args[3]);
            break;
        case 5:
            nrf_fprintf(p_ctx, p_str, p_args[0], p_args[1], p_args[2], p_args[3], p_args[4]);
            break;
        case 6:
            nrf_fprintf(p_ctx, p_str, p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5]);
            break;

        default:
            break;
    }

    postfix_process(p_params, p_ctx, false);
    p_ctx->auto_flush = auto_flush;
}

#define HEXDUMP_BYTES_IN_LINE 8

void nrf_log_hexdump_entry_process(uint8_t * p_data,
                                   uint32_t data_len,
                                   nrf_log_str_formatter_entry_params_t * p_params,
                                   nrf_fprintf_ctx_t * p_ctx)
{
    if (data_len > HEXDUMP_BYTES_IN_LINE)
    {
        return;
    }
    bool auto_flush = p_ctx->auto_flush;
    p_ctx->auto_flush = false;

    prefix_process(p_params, p_ctx);

    uint32_t i;

    for (i = 0; i < HEXDUMP_BYTES_IN_LINE; i++)
    {
        if (i < data_len)
        {
            nrf_fprintf(p_ctx, " %02x", p_data[i]);
        }
        else
        {
            nrf_fprintf(p_ctx, "   ");
        }
    }
    nrf_fprintf(p_ctx, "|");

    for (i = 0; i < HEXDUMP_BYTES_IN_LINE; i++)
    {
        if (i < data_len)
        {
            char c = (char)p_data[i];
            nrf_fprintf(p_ctx, "%c", isprint((int)c) ? c :'.');
        }
        else
        {
            nrf_fprintf(p_ctx, " ");
        }
    }

    postfix_process(p_params, p_ctx, true);

    p_ctx->auto_flush = auto_flush;
}

void nrf_log_str_formatter_timestamp_freq_set(uint32_t freq)
{
    m_timestamp_div = 1;
    /* There is no point to have frequency higher than 1MHz (ns are not printed) and too high
     * frequency leads to overflows in calculations.
     */
    while (freq > 1000000)
    {
        freq /= 2;
        m_timestamp_div *= 2;
    }
    m_freq = freq;
}
#endif //NRF_LOG_ENABLED
