#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "qsee_fs.h"

#include <tees_log.h>

#define DUMP_ROW_LEN 16
#define MAX_LOG_MSG_LEN 1024
#define MEMORY_LOG_TAG "[MEM]"
#define NAME_UNKNOWN "Unknown"

extern TEES_LogLevel g_tees_log_level;

const char *LogUtilsGetTag(TEES_LogLevel level) {
  static const char tag_non[] = "[NON]";
  static const char tag_def[] = "[DEF]";
  static const char tag_vbs[] = "[VBS]";
  static const char tag_dbg[] = "[DBG]";
  static const char tag_inf[] = "[INF]";
  static const char tag_wrn[] = "[WRN]";
  static const char tag_err[] = "[ERR]";
  static const char tag_crt[] = "[CRT]";

  switch (level) {
    case TEES_LOG_LEVEL_DEFAULT: return tag_def;
    case TEES_LOG_LEVEL_VERBOSE: return tag_vbs;
    case TEES_LOG_LEVEL_DEBUG: return tag_dbg;
    case TEES_LOG_LEVEL_INFO: return tag_inf;
    case TEES_LOG_LEVEL_WARNING: return tag_wrn;
    case TEES_LOG_LEVEL_ERROR: return tag_err;
    case TEES_LOG_LEVEL_CRITICAL: return tag_crt;
    default: return tag_non;
  }
}

static int OpenKmsg(void) {
    static const char kmsg_device[] = "/dev/kmsg";
    return open(kmsg_device, O_WRONLY);
}

void PrintLogToKernel(TEES_LogLevel level, const char *function_name, int line, 
                         const char *project_name, const char *fmt, ...) {
  va_list vl;
  char buffer[MAX_LOG_MSG_LEN] = {0};
  char full_buffer[MAX_LOG_MSG_LEN] = {0};
  int full_buf_len = 0;

  if (level < g_tees_log_level ||
      level > TEES_LOG_LEVEL_MAX) {
    return;
  }

  int kmsg_fd = OpenKmsg();
  if (kmsg_fd == -1) {
	  return;
  }

  va_start(vl, fmt);
  vsnprintf(buffer, MAX_LOG_MSG_LEN - 1, fmt, vl);
  va_end(vl);
  full_buf_len = snprintf(full_buffer, MAX_LOG_MSG_LEN - 1, "[%s] %s [%s:%d] %s",
                          project_name ? project_name : NAME_UNKNOWN,
                          LogUtilsGetTag(level),
                          function_name ? function_name : NAME_UNKNOWN,
                          line, buffer);
  write(kmsg_fd, full_buffer, full_buf_len);

  close(kmsg_fd);
}

void PrintMemToKernel(TEES_LogLevel level, const char *function_name, int line,
                    const char *project_name, const void *data,
                    uint32_t data_len)
{
  char full_buf[MAX_LOG_MSG_LEN] = {0};
  int full_buf_len = 0;
  uint32_t written_len = 0;
  char *source_ptr = (char *)data;

  if (level < g_tees_log_level || level > TEES_LOG_LEVEL_MAX || !data) {
    return;
  }

  int kmsg_fd = OpenKmsg();
  if (kmsg_fd == -1) {
	  return;
  }

  full_buf_len = snprintf(full_buf, MAX_LOG_MSG_LEN - 1, 
                          "[%s] %s [%s:%d] "MEMORY_LOG_TAG": %p (%u)\n",
                          project_name ? project_name : NAME_UNKNOWN,
                          LogUtilsGetTag(level),
                          function_name ? function_name : NAME_UNKNOWN,
                          line, data, data_len);
  write(kmsg_fd, full_buf, full_buf_len);

  while (written_len < data_len) {
    char *dest_ptr = full_buf;
    uint32_t bytes_to_write = (data_len - written_len) > DUMP_ROW_LEN ?
                              DUMP_ROW_LEN : (data_len - written_len);

    written_len += bytes_to_write;

    dest_ptr += snprintf(dest_ptr, MAX_LOG_MSG_LEN  - 1, "[%s] ", 
                            project_name ? project_name : NAME_UNKNOWN);

    while (bytes_to_write--) {
      dest_ptr += snprintf(dest_ptr, sizeof(full_buf) - (dest_ptr - full_buf),
                           "%02X", *(source_ptr++));
    }

    write(kmsg_fd, full_buf, dest_ptr - full_buf);
  }

  close(kmsg_fd);
}