#include "../include/log.h"
#include "../include/page.h"
sh_uint8 kernel_log_level=0;
static inline sh_uint8 inb(sh_uint16 port) {
  sh_uint8 val;
  __asm__ volatile ("inb %1, %0":"=a"(val):"Nd"(port));
  return val;
}
static inline void outb(sh_uint16 port,sh_uint8 val) {
  __asm__ volatile ("outb %0, %1"::"a"(val),"Nd"(port));
}
SH_STATUS sh_log_send_byte(sh_uint8 b) {
  while (!(inb(SH_LOG_SERIAL_PORT_COM1+5) & 0x20));
  outb(SH_LOG_SERIAL_PORT_COM1,b);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_send_string(const char* str) {
  while (*str) {
    sh_log_send_byte(*str++);
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_send_uintn(sh_uint64 n) {
  char buf[20];
  sh_uint32 i=0;
  if (n==0) {
    sh_log_send_byte('0');
    return SH_STATUS_SUCCESS;
  }
  while (n>0) {
    buf[i++]='0'+(n%10);
    n/=10;
  }
  while (i--) {
    sh_log_send_byte(buf[i]);
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_send_uintn_hex(sh_uint64 n) {
  char buf[16];
  sh_uint32 i=0;
  const char hex_digits[]="0123456789ABCDEF";
  if (n==0) {
    sh_log_send_byte('0');
    return SH_STATUS_SUCCESS;
  }
  while (n>0) {
    buf[i++]=hex_digits[n & 0xF];
    n>>=4;
  }
  while (i--) {
    sh_log_send_byte(buf[i]);
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_send_double(double value) {
  if (value<0) {
    sh_log_send_byte('-');
    value=-value;
  }
  sh_uint64 integer_part=(sh_uint64)value;
  double fractional_part=value-(double)integer_part;
  SH_STATUS status=sh_log_send_uintn(integer_part);
  if (status!=SH_STATUS_SUCCESS) return status;
  status=sh_log_send_byte('.');
  if (status!=SH_STATUS_SUCCESS) return status;
  for (int i=0;i<6;i++) {
    fractional_part*=10.0;
    sh_uint64 digit=(sh_uint64)fractional_part;
    status=sh_log_send_byte('0'+(sh_uint8)digit);
    if (status!=SH_STATUS_SUCCESS) return status;
    fractional_part-=(double)digit;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_send_payload(sh_log_OUTPUT_PAYLOAD *payload) {
  if (payload==SH_NULLPTR) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  if (!(sh_log_output_type_valid(payload->output_type))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  if (!(sh_log_output_source_valid(payload->output_source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  if (payload->output_source!=SH_LOG_SOURCE_TEST && payload->output_type<kernel_log_level) {
    return SH_STATUS_SUCCESS;
  }
  sh_log_send_string("[Shelter:");
  if (payload->output_source==SH_LOG_SOURCE_MAIN) {
    sh_log_send_string("Main@");
  } else if (payload->output_source==SH_LOG_SOURCE_CONF) {
    sh_log_send_string("Conf@");
  } else if (payload->output_source==SH_LOG_SOURCE_PAGE) {
    sh_log_send_string("Page@");
  } else if (payload->output_source==SH_LOG_SOURCE_SLAB) {
    sh_log_send_string("Slab@");
  } else if (payload->output_source==SH_LOG_SOURCE_TEST) {
    sh_log_send_string("Test@");
  }
  if (payload->output_type==SH_LOG_DEBUG) {
    sh_log_send_string("Debug]    ");
  } else if (payload->output_type==SH_LOG_LOG) {
    sh_log_send_string("Log]      ");
  } else if (payload->output_type==SH_LOG_WARNING) {
    sh_log_send_string("Warning]  ");
  } else if (payload->output_type==SH_LOG_ERROR) {
    sh_log_send_string("Error]    ");
  } else if (payload->output_type==SH_LOG_CRITICAL) {
    sh_log_send_string("Critical] ");
  } else if (payload->output_type==SH_LOG_FATAL) {
    sh_log_send_string("Fatal]    ");
  } else if (payload->output_type==SH_LOG_TEST) {
    sh_log_send_string("Test]     ");
  }
  sh_log_send_uintn(payload->tsc_value);
  sh_log_send_string(" : ");
  sh_log_send_string(payload->message_pointer);
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_load_log_level(sh_uint8 log_level) {
  kernel_log_level=log_level;
  return SH_STATUS_SUCCESS;
}
sh_uint8 sh_log_get_log_level() {
  return kernel_log_level;
}
SH_STATUS sh_log_test(const char* str) {
  if (str==SH_NULLPTR) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_TEST,
    .output_source=SH_LOG_SOURCE_TEST,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_debug(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_DEBUG,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_log(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_LOG,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_warning(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_WARNING,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_error(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_ERROR,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_critical(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_CRITICAL,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_fatal(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_FATAL,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_ltest(const char* str) {
  if (str==SH_NULLPTR) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_TEST,
    .output_source=SH_LOG_SOURCE_TEST,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_ldebug(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_DEBUG,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_llog(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_LOG,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_lwarning(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_WARNING,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_lerror(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_ERROR,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_lcritical(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_CRITICAL,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_lfatal(const char* str,sh_log_OUTPUT_SOURCE source) {
  if (str==SH_NULLPTR || !(sh_log_output_source_valid(source))) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_log_OUTPUT_PAYLOAD payload={
    .output_type=SH_LOG_FATAL,
    .output_source=source,
    .tsc_value=sh_tsc_get_kernel_current_tsc(),
    .message_pointer=str
  };
  SH_STATUS status=sh_log_send_payload(&payload);
  if (sh_status_error(status)) {
    return status;
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_log_mem_stats(sh_log_OUTPUT_SOURCE source) {
  sh_page_MEM_STATS mem_stats;
  sh_page_get_memory_stats(&mem_stats);
  if (sh_log_get_log_level()<=1) {
    sh_log_llog("Total memory installed (bytes) : ",source);
    sh_log_send_uintn(mem_stats.memory_total_bytes);
    sh_log_send_string(" / 0x");
    sh_log_send_uintn_hex(mem_stats.memory_total_bytes);
    sh_log_send_string("\n");
    sh_log_llog("Total memory installed (pages) : ",source);
    sh_log_send_uintn(mem_stats.memory_total_pages);
    sh_log_send_string(" / 0x");
    sh_log_send_uintn_hex(mem_stats.memory_total_pages);
    sh_log_send_string("\n");
    sh_log_llog("Free memory : ",source);
    sh_log_send_double(mem_stats.free_ratio*100);
    sh_log_send_string("%\n");
    sh_log_llog("Used memory : ",source);
    sh_log_send_double(mem_stats.used_ratio*100);
    sh_log_send_string("%\n");
    sh_log_llog("Free pages : ",source);
    sh_log_send_uintn(mem_stats.free_pages);
    sh_log_send_string("\n");
    sh_log_llog("Used pages : ",source);
    sh_log_send_uintn(mem_stats.used_pages);
    sh_log_send_string("\n");
    sh_log_llog("Largest free block (pages) : ",source);
    sh_log_send_uintn(mem_stats.largest_free_block);
    sh_log_send_string("\n");
    sh_log_llog("Largest used block (pages) : ",source);
    sh_log_send_uintn(mem_stats.largest_used_block);
    sh_log_send_string("\n");
    sh_log_llog("Free block count : ",source);
    sh_log_send_uintn(mem_stats.free_blocks_count);
    sh_log_send_string("\n");
    sh_log_llog("Used block count : ",source);
    sh_log_send_uintn(mem_stats.used_blocks_count);
    sh_log_send_string("\n");
    sh_log_llog("Total memory taken by physical bitmap (bytes) : ",source);
    sh_log_send_uintn(mem_stats.physical_bitmap_size_bytes);
    sh_log_send_string(" / 0x");
    sh_log_send_uintn_hex(mem_stats.physical_bitmap_size_bytes);
    sh_log_send_string("\n");
    sh_log_llog("Total memory taken by physical bitmap (pages) : ",source);
    sh_log_send_uintn(mem_stats.physical_bitmap_size_pages);
    sh_log_send_string(" / 0x");
    sh_log_send_uintn_hex(mem_stats.physical_bitmap_size_pages);
    sh_log_send_string("\n");
  }
  return SH_STATUS_SUCCESS;
}
