/******************************************************************************
 * file    lib/management/better_log.c 
 * author  YuLiang
 * version 1.0.0
 * date    14-Sep-2021
 * brief   This file provides all the log operation functions.
 *
 ******************************************************************************
 * Attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2021 LandPower</center></h2>
 *
 * 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 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 LandPower nor the names of its contributors may be used to 
 *      endorse or promote products derived from this software without specific
 *      prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS 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, 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.
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* 标准C库头文件. */
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <execinfo.h>

/* 用户代码头文件. */
#include <fifo.h>

/* Private define ------------------------------------------------------------*/
#define LOG_DB_LOCK pthread_mutex_lock(&log_sys->log_db_mutex)
#define LOG_DB_UNLOCK pthread_mutex_unlock(&log_sys->log_db_mutex)

#define LOG_OUT 0
#define LOG_SHOW 1
/* Private macro -------------------------------------------------------------*/

/* Private typedef -----------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
log_sys_t *log_sys;
static char *log_str = NULL;

/* log等级. */
static const log_lvl_t log_priority[] =
{
    {LOG_LVL_ERR, "err"},
    {LOG_LVL_WARN, "warn"},
    {LOG_LVL_NOTIF, "notif"},
    {LOG_LVL_INFO, "info"},
    {LOG_LVL_DBG, "debug"},
    {-1, NULL}
};

/* log模块. */
static const log_module_t log_module_names[] =
{
    {LOG_DEFAULT, "DEFAULT"},
    {LOG_CLI, "CLI"},
    {LOG_MEMORY, "MEMORY"},
    {LOG_FIFO, "FIFO"},
    {LOG_GPIO, "GPIO"},
    {LOG_COLL, "COLLECT"},

    {LOG_CA_DB, "CA_DB"},
    {LOG_CA_DBG, "CA_DBG"},
    {LOG_CA_MQTT, "CA_MQTT"},
    {LOG_CA_LAND, "CA_LAND"},
    {-1, NULL}
};

/* Private function prototypes -----------------------------------------------*/

/* Internal functions --------------------------------------------------------*/
/* 文件log输出底层函数. */
static void _log_out_db(LOG_MODULE_E module, LOG_LVL_E priority, const char *va_str)
{
    log_sys_t *log = log_sys;

    char time_str[TIME_STR_LEN] = {0};
    char *zErrMsg = NULL;
    static uint64_t  logCnt = 0;
    time_string(log->timestamp_precision, time_str, sizeof(time_str));

    LOG_DB_LOCK;

    snprintf(log_str, LOG_CMD_LEN, 
        "INSERT INTO better_log (LEVEL,LOG) VALUES (%d, \"%s %s-%s: %s\"); ",
        priority, time_str, log_priority[priority].describe, log_module_names[module].describe, va_str );    //id 已设为自增
    
    if (sqlite3_exec( log->db, log_str, 0, 0, &zErrMsg ) != SQLITE_OK)
    {
        printh("SQL INSERT error: %s\r\n", zErrMsg);
        sqlite3_free(zErrMsg);
    }
        
    /*定量清除log*/
    logCnt++;
    if (logCnt >= 100)
    {
        clock_t begin,end;
        begin = clock();
        log_clean();
        end = clock();
        printh("Log clean runtimer : %lf\r\n",(double)(end-begin)/CLOCKS_PER_SEC);
        logCnt = 0;
    }

    LOG_DB_UNLOCK;
}

/* 解日志输出fifo数据封装 */
static void _log_out(_log_out_t *data)
{
    _log_out_db(data->module, data->lvl, data->log_out_str);
}

/* 串口日志输出 */
static void _log_out_std( LOG_MODULE_E module, LOG_LVL_E priority, const char *va_str )
{
    log_sys_t *log = log_sys;
        
    char time_str[TIME_STR_LEN] = {0};
    time_string(log->timestamp_precision, time_str, sizeof(time_str));

    printh("%s %s-%s: %s\n", time_str, log_priority[priority].describe, log_module_names[module].describe, va_str);
}

/* 将日志按照格式串口打印 */
static int _log_show_print(void *data, int argc, char **argv, char **azColName)
{
    printh("LOG id %s: %s\r\n", argv[0], argv[2]);

    return 0;
}

/* log打印底层函数 */
static void _log_show(_log_show_t *data)
{
    log_sys_t *log = log_sys;
    char *zErrMsg = NULL;

    LOG_DB_LOCK;

    memset(log_str, 0, LOG_CMD_LEN);
    switch (data->type)
    {
    case LOG_SHOW_CNT:      //打印指定数量的日志
        snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log ORDER BY id DESC LIMIT %d;", data->param);
        break;

    case LOG_SHOW_LVL:      //打印指定等级的日志
        snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log WHERE LEVEL = %d ORDER BY id DESC LIMIT 100;", data->param);
        break;

    case LOG_SHOW_KEYWORD:  //打印含关键词的日志
        if (data->param <= 0)
        {
            snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log WHERE LOG GLOB \'*%s*\' ORDER BY id DESC;",
                      data->log_show_str);
        }
        else
        {
            snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log WHERE LOG GLOB \'*%s*\' ORDER BY id DESC LIMIT %d;",
                      data->log_show_str, data->param);
        }
        break;
    
    default:    //打印所有日志
        snprintf(log_str, LOG_CMD_LEN, "SELECT * FROM better_log ORDER BY id DESC;");
        break;
    }
    
    if (sqlite3_exec(log->db, log_str, _log_show_print, 0, &zErrMsg) != SQLITE_OK)
    {
        printh("SQL show error: %s\r\n", zErrMsg);
        sqlite3_free(zErrMsg);
    }

    LOG_DB_UNLOCK;
}

/* 封装数据并写fifo */
int32_t _log_msg_send(uint32_t type, void *data)
{
    _log_msg_t log_msg;
    
    /* 封装消息. */
    log_msg.type = type;
    log_msg.data = data;

    /* 发送消息 */
    if (fifo_write(log_sys->log_fifo_id, (void*)(&log_msg), sizeof(_log_msg_t)) != sizeof(_log_msg_t))
    {
        DBG(DBG_M_FIFO, "LOG write ERROR!\r\n");
        return E_ERROR;
    }

    return E_NONE;
}

/* 经由fifo输出日志 */
void _log_fifo_out(LOG_MODULE_E module, LOG_LVL_E priority, const char *va_str)
{
    _log_out_t *log_data = NULL;

    log_data = XMALLOC_Q(MTYPE_LOG, sizeof(_log_out_t));
    if (!log_data)
    {
        return;
    }
    
    log_data->module = module;
    log_data->lvl = priority;
    snprintf(log_data->log_out_str, LOG_STR_LEN, va_str);

    _log_msg_send(LOG_OUT, log_data);
}

/* 读取log fifo数据循环线程 */
void *_log_handle(void *arg)
{
    _log_msg_t *recv_msg = NULL;

    while(1)
    {
        if (fifo_read(log_sys->log_fifo_id, (void **)&recv_msg) != 0)
        {
            DBG(DBG_M_FIFO, "ERROR at fifo %d read!\r\n", log_sys->log_fifo_id);
            continue;
        }

        if (recv_msg->type == LOG_OUT)  //输出日志，写数据库
        {
            _log_out((_log_out_t*) recv_msg->data);
        }
        else if (recv_msg->type == LOG_SHOW)    //打印日志，读数据库
        {
            _log_show((_log_show_t*) recv_msg->data);
        }

        XFREE(MTYPE_LOG, recv_msg->data);
        fifo_push(log_sys->log_fifo_id);
    }
    return NULL;
}

/* Interface functions -------------------------------------------------------*/
/* log打印输出函数. */
#define BLOG_FUNC(FUNCNAME,PRIORITY) \
void FUNCNAME(LOG_MODULE_E module, const char *format, ...) \
{ \
    char str[LOG_STR_LEN] = {0}; \
    log_sys_t *log = log_sys; \
    if (NULL == log) return; \
    va_list args; \
    va_start(args, format); \
    vsnprintf(str, LOG_STR_LEN, format, args); \
    va_end(args); \
    if ((1 << PRIORITY) & log->enable_lvl[LOG_MODE_STDOUT]) \
        _log_out_std(module, PRIORITY, str); \
    if (((1 << PRIORITY) & log->enable_lvl[LOG_MODE_FILE]) && log->db){ \
        if (log_sys->log_fifo_id) \
            _log_fifo_out(module, PRIORITY, str); \
        else \
            _log_out_db(module, PRIORITY, str);} \
}

BLOG_FUNC(log_err, LOG_LVL_ERR)
BLOG_FUNC(log_warn, LOG_LVL_WARN)
BLOG_FUNC(log_info, LOG_LVL_INFO)
BLOG_FUNC(log_notice, LOG_LVL_NOTIF)
BLOG_FUNC(log_debug, LOG_LVL_DBG)
#undef BLOG_FUNC

/* description: log输出.
   param:   module      --  指定模块
            priority    --  指定优先级
   return: */
void log_out(LOG_MODULE_E module, LOG_LVL_E priority, const char *format, ...)
{
    char str[LOG_STR_LEN] = {0}; \
    log_sys_t *log = log_sys;
    
    if (NULL == log) return;

    va_list args;
    va_start(args, format);
    vsnprintf(str, LOG_STR_LEN, format, args);
    va_end (args);
    
    /* 串口log. */
    if ((1 << priority) & log->enable_lvl[LOG_MODE_STDOUT])
        _log_out_std(module, priority, str);
    
    /* 判断是否是文件输出 */
    if (((1 << priority) & log->enable_lvl[LOG_MODE_FILE]) && log->db)
        _log_out_db(module, priority, str);
}

/* 打印日志函数 */
void log_show(int32_t show_cnt, LOG_LVL_E priority, const char *key_word)
{
    _log_show_t *log_data = NULL;

    log_data = XMALLOC_Q(MTYPE_LOG, sizeof(_log_show_t));
    if (!log_data)
    {
        return;
    }
    
    if (priority != LOG_LVL_MAX)      // 按等级打印日志
    {
        log_data->type = LOG_SHOW_LVL;
        log_data->param = priority;
    }
    else if (key_word != NULL)      // 高级打印功能
    {
        log_data->type = LOG_SHOW_KEYWORD;
        log_data->param = show_cnt;
        snprintf(log_data->log_show_str, LOG_STR_LEN, key_word);
    }
    else if (show_cnt > 0)   // 按数量打印日志
    {
        log_data->type = LOG_SHOW_CNT;
        log_data->param = show_cnt;
    }
    else
    {
        log_data->type = LOG_SHOW_MAX;
    }

    _log_msg_send(LOG_SHOW, log_data);
}

/* description: log系统初始化.
   param:
   return: (0)完成,(-1)失败 */
int32_t log_open()
{
    log_sys_t *log = NULL;
    int32_t i = 0;
    int32_t rv = 0;
    char *sql = NULL;
    char *zErrMsg = NULL;

    /* 申请内存. */
    log_str = XMALLOC(MTYPE_LOG, LOG_CMD_LEN);
    log_sys = XMALLOC(MTYPE_LOG, sizeof(log_sys_t));
    
    if (NULL == log_sys)
    {
       return E_MEM;
    }
    
    log = log_sys;

    pthread_mutex_init(&log->log_db_mutex, NULL);

    /* 打开 log 数据库. */
    log->filename = XSTRDUP(MTYPE_LOG, LOG_FILE);
    rv = sqlite3_open(log->filename, &log->db);
    if (rv)
    {
        log->db = NULL;
        printf("Can't open database: %s\r\n", sqlite3_errmsg(log->db));
        return E_SYS_CALL;
    }

    /* 创建表. */
    sql = "CREATE TABLE IF NOT EXISTS better_log(" \
         "ID INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL," \
         "LEVEL              INT8 NOT NULL," \
         "LOG                TEXT);";
    if (sqlite3_exec(log->db, sql, 0, 0, &zErrMsg) != SQLITE_OK)
    {
        printf("SQL create error: %s\r\n", zErrMsg);
        sqlite3_free(zErrMsg);
    }
    
    /* 设置默认log级别输出方式 */
    for (i = 0; i < LOG_MODE_MAX; i++)
    {
        log->enable_lvl[i] = 0;
    }
    log->enable_lvl[LOG_MODE_FILE] = (1 << LOG_LVL_ERR) | (1 << LOG_LVL_WARN) | (1 << LOG_LVL_NOTIF);
    log->enable_lvl[LOG_MODE_STDOUT] = (1 << LOG_LVL_ERR) | (1 << LOG_LVL_WARN) | (1 << LOG_LVL_NOTIF);
    log->enable_lvl[LOG_MODE_MONITOR] = (1 << LOG_LVL_ERR) | (1 << LOG_LVL_WARN) | (1 << LOG_LVL_NOTIF);
    log->default_lvl = 0;
    
    return E_NONE;
}

/* description: 配置对应等级的log的输出方式.
   param:   mode      --  输出方式
            log_level   --  输出等级
   return:  */
void log_set_level(LOG_MODE_E mode, LOG_LVL_E log_level)
{
    log_sys->enable_lvl[mode] |= (1 << log_level);
}

/* description: 取消对应等级的log的输出方式.
   param:   mode      --  输出方式
            log_level   --  输出等级
   return:  */
void log_unset_level(LOG_MODE_E mode, LOG_LVL_E log_level)
{
    log_sys->enable_lvl[mode] &= ~(1 << log_level);
}

/* description: 根据传入的字符串返回相应的log优先级.
   param:   lvl_name    --  输出等级的字符串
   return:  (LOG_LVL_E)输出等级 */
int32_t log_level_get_by_name(const char *lvl_name)
{
    int32_t level = LOG_LVL_MAX;
  
    for (level = 0 ; log_priority[level].describe != NULL ; level++)
    if (!strncmp(lvl_name, log_priority[level].describe, 2))
        return log_priority[level].lvl;

    return LOG_LVL_MAX;
}

/* 打印堆栈使用情况:  */
/* description: 根据传入的字符串返回相应的log优先级.
   param:   priority    --  log优先级
   return: */
void log_backtrace(int32_t priority)
{
    void *array[BACKTRACE_SIZE] = {NULL};
    int32_t size = 0;
    int32_t i = 0;
    char **strings = NULL;

    size = backtrace(array, BACKTRACE_SIZE);
    if ((size <= 0) || ((size_t)size > BACKTRACE_SIZE))
    {
        log_err(LOG_DEFAULT, "Cannot get backtrace, returned invalid # of frames %d "
                "(valid range is between 1 and %d)", size, BACKTRACE_SIZE);
        return;
    }
    
    log_out(LOG_DEFAULT, priority, "Backtrace for %d stack frames:", size);
    strings = backtrace_symbols(array, size);
    if (!strings)
    {
        log_out(LOG_DEFAULT, priority, "Cannot get backtrace symbols (out of memory?)");
        for (i = 0; i < size; i++)
            log_out(LOG_DEFAULT, priority, "[bt %d] %p", i, array[i]);
    }
    else
    {
        for (i = 0; i < size; i++)
            log_out(LOG_DEFAULT, priority, "[bt %d] %s",i,strings[i]);
        free(strings);
    }
}

/* 删除多余的 log 保留 5000 条. */
void log_clean(void)
{
    log_sys_t *log = log_sys;
    char *zErrMsg = NULL;
    const char *sql = "delete from better_log where id<(select Max(id) from better_log)-5000;";
    
    if (sqlite3_exec(log->db, sql, 0, 0, &zErrMsg) != SQLITE_OK)
    {
        printh("SQL delete error: %s\r\n", zErrMsg);
        sqlite3_free(zErrMsg);
    }
}

int32_t log_handle_init(void)
{
    struct sched_param param;
    pthread_attr_t attr;
    pthread_t pid;
    int rv = 0;

    /* 初始化log fifo */
    log_sys->log_fifo_id = fifo_create(LOG_DB_FIFO, 32);
    if (log_sys->log_fifo_id < 0)
    {
        log_out(LOG_DEFAULT, LOG_LVL_ERR, "Open fifo " LOG_DB_FIFO " error.");
        return E_ERROR;
    }

    /* 配置线程RR调度，优先级50 */
    pthread_attr_init(&attr);
    param.sched_priority = 25;
    pthread_attr_setschedpolicy(&attr, SCHED_RR);
    pthread_attr_setschedparam(&attr, &param);
    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
    rv = pthread_create(&pid, &attr, _log_handle, NULL);
    if (rv != 0)
    {
        log_out(LOG_DEFAULT, LOG_LVL_ERR, "PD can't create log db pthread %d.", rv);
        return E_SYS_CALL;
    }
    else
    {
        thread_m_add("LOG_DB_THREAD", pid);
    }
    pthread_attr_destroy(&attr);

    return E_NONE;
}
/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/
