#include "../include/page.h"
#include <stdint.h>
__attribute__((section(".bss")))
static sh_uint8 memory_map_buffer[64*1024];
static sh_uint8 *physical_bitmap;
__attribute__((section(".bss")))
static sh_uint64 physical_memory_pages_count=0;
__attribute__((section(".bss")))
static sh_uint64 physical_memory_bytes_count=0;
__attribute__((section(".bss")))
static sh_uint64 physical_bitmap_size_bytes=0;
__attribute__((section(".bss")))
static sh_uint64 physical_bitmap_size_pages=0;
static sh_page_VIRTUAL_ADRESS page_table_pool_va_ptr=SH_PAGE_NULL_VA;
SH_STATUS sh_page_load_boot_ptp_va(sh_page_VIRTUAL_ADRESS pt_pool_va) {
  page_table_pool_va_ptr=pt_pool_va;
  sh_log_ldebug("Page table pool VA: 0x",SH_LOG_SOURCE_PAGE);
  sh_log_send_uintn_hex((sh_uint64)page_table_pool_va_ptr);
  sh_log_send_string("\n");
  sh_uint8 first_byte=*(sh_uint8*)(page_table_pool_va_ptr);
  sh_log_debug("If you can see this message, no fault happened.",SH_LOG_SOURCE_PAGE);
  return SH_STATUS_SUCCESS;
}
sh_page_VIRTUAL_ADRESS sh_page_get_boot_ptp_va() {
  return page_table_pool_va_ptr;
}
SH_STATUS sh_page_copy_memory_map() {
  return sh_mem_copy(memory_map_buffer,(void*)SH_PAGE_MEMORY_MAP_VA,sizeof(memory_map_buffer));
}
SH_STATUS sh_page_check_memory_map() {
  static const sh_uint8 memory_map_sig[8]={'S','h','e','M','m','a','p','B'};
  if (sh_mem_compare(memory_map_sig,memory_map_buffer,sizeof(memory_map_sig))==SH_STATUS_MEM_NOT_EQUAL) {
    sh_log_critical("Memory map doesn't have signature on.",SH_LOG_SOURCE_PAGE);
    return SH_STATUS_INVALID_SIGNATURE;
  }
  sh_page_MEMORY_MAP_HEADER *memory_map_header=(sh_page_MEMORY_MAP_HEADER *)memory_map_buffer;
  sh_log_ldebug("Memory map entry count: ",SH_LOG_SOURCE_PAGE);
  sh_log_send_uintn((sh_uint64)memory_map_header->entry_count);
  sh_log_send_string("\n");
  sh_log_ldebug("Memory map entry size: ",SH_LOG_SOURCE_PAGE);
  sh_log_send_uintn((sh_uint64)memory_map_header->entry_size);
  sh_log_send_string("\n");
  sh_log_ldebug("Memory map syntax version: ",SH_LOG_SOURCE_PAGE);
  sh_log_send_uintn((sh_uint64)memory_map_header->mmap_syntax_version);
  sh_log_send_string("\n");
  if (memory_map_header->entry_count*memory_map_header->entry_size+sizeof(sh_page_MEMORY_MAP_HEADER)>sizeof(memory_map_buffer)) {
    sh_log_error("Memory map overflow allocated buffer.",SH_LOG_SOURCE_PAGE);
    return SH_STATUS_MMAP_BUFFER_OVERFLOW;
  }
  return SH_STATUS_SUCCESS;
}
void sh_page_dump_memory_map() {
  sh_page_MEMORY_MAP_HEADER *memory_map_header=(sh_page_MEMORY_MAP_HEADER *)memory_map_buffer;
  sh_log_send_string("Memory map dump:\n");
  sh_log_send_string("Header:\n");
  for (sh_uint64 i=0;i<sizeof(sh_page_MEMORY_MAP_HEADER);++i) {
    sh_log_send_uintn((sh_uint64)memory_map_buffer[i]);
    sh_log_send_string(" ");
  }
  sh_log_send_string("\n");
  for (sh_uint64 i=0;i<memory_map_header->entry_count;++i) {
    sh_log_send_string("Entry number ");
    sh_log_send_uintn(i);
    sh_log_send_string(" : ");
    for (sh_uint64 y=0;y<sizeof(sh_page_MEMORY_MAP_ENTRY);++y) {
      sh_log_send_uintn((sh_uint64)memory_map_buffer[sizeof(sh_page_MEMORY_MAP_HEADER)+i*sizeof(sh_page_MEMORY_MAP_ENTRY)+y]);
      sh_log_send_string(" ");
    }
    sh_log_send_string("\n");
  }
}
sh_uint64 sh_page_get_physical_memory_amount_pages() {
  return physical_memory_pages_count;
}
sh_uint64 sh_page_get_physical_memory_amount_bytes() {
  return physical_memory_bytes_count;
}
sh_uint64 sh_page_get_one_page_na() {
  sh_uint64 page_count=physical_memory_pages_count;
  sh_uint64 bitmap_word_count=(page_count+63)/64;
  for (sh_uint64 word=0;word<bitmap_word_count;word++) {
    sh_uint64 value=physical_bitmap[word];
    if (value==0xFFFFFFFFFFFFFFFFULL) {
      continue;
    }
    for (sh_uint64 bit=0;bit<64;bit++) {
      sh_uint64 page_index=(word*64)+bit;
      if (page_index>=page_count) {
        return 0;
      }
      if ((value & (1ULL<<bit))==0) {
        return page_index*SH_PAGE_SIZE;
      }
    }
  }
  return 0;
}
SH_STATUS sh_page_set_pages_range_bitmap(sh_uint8 *bitmap,sh_uint64 page_count_in_bitmap,sh_uint64 page_index,sh_uint64 page_count,sh_bool state) {
  if (bitmap==SH_NULLPTR) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  if (page_index+page_count>page_count_in_bitmap) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  for (sh_uint64 i=0;i<page_count;++i) {
    sh_uint64 page=page_index+i;
    sh_uint64 byte_index=page/8;
    sh_uint8 bit_index=page%8;
    if (state) {
      bitmap[byte_index]|=(sh_uint8)(1u<<bit_index);
    } else {
      bitmap[byte_index]&=(sh_uint8)~(1u<<bit_index);
    }
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_init_ptp(sh_page_PHYSICAL_ADRESS ptp_pa,sh_page_VIRTUAL_ADRESS ptp_va,sh_uint64 initial_fill_level,sh_page_PAGE_TABLE_POOL *page_table_pool) {
  page_table_pool->page_table_pa=ptp_pa;
  page_table_pool->page_table_va=ptp_va;
  page_table_pool->ptp_pages_count=SH_PAGE_PTP_ALLOCATOR_PAGES_COUNT;
  page_table_pool->ptp_alloc_bitmap_uint64_count=SH_PAGE_PTP_ALLOCATOR_BITMAP_UINT64;
  SH_STATUS status=sh_mem_set_8((sh_uint8*)page_table_pool->ptp_alloc_bitmap,SH_FALSE,sizeof(page_table_pool->ptp_alloc_bitmap));
  if (sh_status_error(status)) {
    sh_log_error("Error: couldn't initialize page table pool bitmap.",SH_LOG_SOURCE_PAGE);
    return SH_STATUS_PT_POOL_NO_BITMAP_INIT;
  }
  if (initial_fill_level!=0) {
    status=sh_page_set_pages_range_bitmap((sh_uint8*)page_table_pool->ptp_alloc_bitmap,page_table_pool->ptp_pages_count,0,initial_fill_level,SH_TRUE);
    if (sh_status_error(status)) {
      sh_log_error("Error: couldn't initialize pages tables already alocated.",SH_LOG_SOURCE_PAGE);
      return SH_STATUS_PT_POOL_NO_PAGE_SET;
    }
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_dump_ptp_bitmap(sh_page_PAGE_TABLE_POOL *ptp) {
  for (sh_uint64 i=0;i<ptp->ptp_alloc_bitmap_uint64_count;++i) {
    sh_log_send_string(" 0x");
    sh_log_send_uintn_hex(ptp->ptp_alloc_bitmap[i]);
  }
  sh_log_send_string("\n");
  return SH_STATUS_SUCCESS;
}
sh_page_PHYSICAL_ADRESS sh_page_ptp_alloc_one_page(sh_page_PAGE_TABLE_POOL *pt_pool) {
  if (pt_pool==SH_NULLPTR) {
    return SH_STATUS_INVALID_PARAMETER;
  }
  sh_uint64 page_count=pt_pool->ptp_pages_count;
  sh_uint64 bitmap_word_count=(page_count+63)/64;
  for (sh_uint64 word=0;word<bitmap_word_count;word++) {
    sh_uint64 value=pt_pool->ptp_alloc_bitmap[word];
    if (value==0xFFFFFFFFFFFFFFFFULL) {
      continue;
    }
    for (sh_uint64 bit=0;bit<64;bit++) {
      sh_uint64 page_index=(word*64)+bit;
      if (page_index>=page_count) {
        return 0;
      }
      if ((value & (1ULL<<bit))==0) {
        pt_pool->ptp_alloc_bitmap[word]|=(1ULL<<bit);
        sh_page_PHYSICAL_ADRESS pa=pt_pool->page_table_pa+page_index*SH_PAGE_SIZE;
        return pa;
      }
    }
  }
  return 0;
}
SH_STATUS sh_page_map_one_page_ptp(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va,sh_page_PHYSICAL_ADRESS pa,sh_uint64 flags) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0 || pa%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pml4_i=(va>>39) & 0x1FF;
  sh_uint64 pdpt_i=(va>>30) & 0x1FF;
  sh_uint64 pd_i=(va>>21) & 0x1FF;
  sh_uint64 pt_i=(va>>12) & 0x1FF;
  sh_uint64 *pdpt;
  sh_uint64 *pd;
  sh_uint64 *pt;
  sh_uint64 *pml4=(sh_uint64*)ptp->page_table_va;
  if (!(pml4[pml4_i] & SH_PAGE_PRESENT)) {
    sh_page_PHYSICAL_ADRESS pdpt_pa=sh_page_ptp_alloc_one_page(ptp);
    if (!pdpt_pa) return SH_STATUS_OUT_OF_MEMORY;
    pdpt=sh_page_ptp_pa_to_va(ptp,pdpt_pa);
    if (!pdpt) return SH_STATUS_INVALID_INTERNAL_PA;
    sh_mem_set_8((sh_uint8*)pdpt,0,SH_PAGE_SIZE);
    pml4[pml4_i]=pdpt_pa | SH_PAGE_TABLE_FLAGS | SH_PAGE_PRESENT;
  } else {
    pdpt=sh_page_ptp_pa_to_va(ptp,(pml4[pml4_i] & ~0xFFFULL));
    if (!pdpt) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pdpt[pdpt_i] & SH_PAGE_PRESENT)) {
    sh_page_PHYSICAL_ADRESS pd_pa=sh_page_ptp_alloc_one_page(ptp);
    if (!pd_pa) return SH_STATUS_OUT_OF_MEMORY;
    pd=sh_page_ptp_pa_to_va(ptp,pd_pa);
    if (!pd) return SH_STATUS_INVALID_INTERNAL_PA;
    sh_mem_set_8((sh_uint8*)pd,0,SH_PAGE_SIZE);
    pdpt[pdpt_i]=pd_pa | SH_PAGE_TABLE_FLAGS | SH_PAGE_PRESENT;
  } else {
    pd=sh_page_ptp_pa_to_va(ptp,(pdpt[pdpt_i] & ~0xFFFULL));
    if (!pd) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pd[pd_i] & SH_PAGE_PRESENT)) {
    sh_page_PHYSICAL_ADRESS pt_pa=sh_page_ptp_alloc_one_page(ptp);
    if (!pt_pa) return SH_STATUS_OUT_OF_MEMORY;
    pt=sh_page_ptp_pa_to_va(ptp,pt_pa);
    if (!pt) return SH_STATUS_INVALID_INTERNAL_PA;
    sh_mem_set_8((sh_uint8*)pt,0,SH_PAGE_SIZE);
    pd[pd_i]=pt_pa | SH_PAGE_TABLE_FLAGS | SH_PAGE_PRESENT;
  } else {
    pt=sh_page_ptp_pa_to_va(ptp,pd[pd_i] & ~0xFFFULL);
    if (!pt) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  pt[pt_i]=(pa & ~0xFFFULL) | flags | SH_PAGE_PRESENT;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_is_va_mapped_ptp(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pml4_i=(va>>39) & 0x1FF;
  sh_uint64 pdpt_i=(va>>30) & 0x1FF;
  sh_uint64 pd_i=(va>>21) & 0x1FF;
  sh_uint64 pt_i=(va>>12) & 0x1FF;
  sh_uint64 *pdpt;
  sh_uint64 *pd;
  sh_uint64 *pt;
  sh_uint64 *pml4=(sh_uint64*)ptp->page_table_va;
  if (!(pml4[pml4_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_VA_NOT_MAPPED;
  } else {
    pdpt=sh_page_ptp_pa_to_va(ptp,(pml4[pml4_i] & ~0xFFFULL));
    if (pdpt==0) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pdpt[pdpt_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_VA_NOT_MAPPED;
  } else {
    pd=sh_page_ptp_pa_to_va(ptp,(pdpt[pdpt_i] & ~0xFFFULL));
    if (pd==0) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pd[pd_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_VA_NOT_MAPPED;
  } else {
    pt=sh_page_ptp_pa_to_va(ptp,(pd[pd_i] & ~0xFFFULL));
    if (pt==0) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pt[pt_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_VA_NOT_MAPPED;
  } else {
    return SH_STATUS_VA_MAPPED;
  }
}
SH_STATUS sh_page_is_va_range_mapped_ptp(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va,sh_uint64 size_bytes) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0 || size_bytes%SH_PAGE_SIZE!=0 || size_bytes==0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 counter=0;
  for (sh_uint64 i=0;i<size_bytes/SH_PAGE_SIZE;i++) {
    if (sh_page_is_va_mapped_ptp(ptp,va+i*SH_PAGE_SIZE)==SH_STATUS_VA_MAPPED) {
      counter++;
    }
  }
  if (counter==0) return SH_STATUS_VA_NOT_MAPPED;
  if (counter==size_bytes/SH_PAGE_SIZE) return SH_STATUS_VA_FULLY_MAPPED;
  return SH_STATUS_VA_PARTIALLY_MAPPED;
}
SH_STATUS sh_page_search_available_va_range(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS range_base,sh_page_VIRTUAL_ADRESS range_size_bytes,sh_uint64 size_bytes,sh_page_VIRTUAL_ADRESS *adress_found) {
  if (ptp==SH_NULLPTR || adress_found==SH_NULLPTR) {
    sh_log_send_string("1\n");
    return SH_STATUS_INVALID_PARAMETER;
  }
  if (size_bytes==0 || size_bytes%SH_PAGE_SIZE!=0) {
    sh_log_send_string("2\n");
    return SH_STATUS_INVALID_PARAMETER;
  };
  if (range_base%SH_PAGE_SIZE!=0 || range_size_bytes%4096!=0 || range_size_bytes==0) {
    sh_log_send_string("3\n");
    return SH_STATUS_INVALID_PARAMETER;
  };
  sh_uint64 pages_needed=size_bytes/SH_PAGE_SIZE;
  sh_page_VIRTUAL_ADRESS current_va=range_base;
  sh_uint64 contiguous=0;
  sh_page_VIRTUAL_ADRESS candidate_start=0;
  while (current_va<range_base+range_size_bytes) {
    SH_STATUS status=sh_page_is_va_mapped_ptp(ptp,current_va);
    if (status==SH_STATUS_VA_NOT_MAPPED) {
      if (contiguous==0) {
        candidate_start=current_va;
      }
      contiguous++;
      if (contiguous==pages_needed) {
        *adress_found=(sh_page_VIRTUAL_ADRESS)candidate_start;
        return SH_STATUS_SUCCESS;
      }
    } else if (status==SH_STATUS_VA_MAPPED) {
        contiguous=0;
    } else {
      return SH_STATUS_INVALID_INTERNAL_PA;
    }
    current_va+=SH_PAGE_SIZE;
  }
  return SH_STATUS_OUT_OF_MEMORY;
}
SH_STATUS sh_page_map_contiguous_pages_range_ptp(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va,sh_page_PHYSICAL_ADRESS pa,sh_uint64 flags,sh_uint64 size_bytes) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0 || pa%SH_PAGE_SIZE!=0 || size_bytes==0 || size_bytes%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  if ((flags & SH_PAGE_PS)==SH_PAGE_PS) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pages=size_bytes/SH_PAGE_SIZE;
  SH_STATUS status=sh_page_is_va_range_mapped_ptp(ptp,va,size_bytes);
  if (status==SH_STATUS_VA_PARTIALLY_MAPPED) return SH_STATUS_ERROR_VA_PARTIALLY_MAPPED;
  if (status==SH_STATUS_VA_FULLY_MAPPED) return SH_STATUS_ERROR_VA_FULLY_MAPPED;
  for (sh_uint64 i=0;i<pages;i++) {
    status=sh_page_map_one_page_ptp(ptp,va+i*SH_PAGE_SIZE,pa+i*SH_PAGE_SIZE,flags);
    if (status!=SH_STATUS_SUCCESS) {
      return status;
    }
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_search_physical_contiguous_block_na(sh_uint64 pages_needed,sh_page_PHYSICAL_ADRESS *pa) {
  if (pages_needed==0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 page_count=sh_page_get_physical_memory_amount_pages();
  sh_uint64 contiguous=0;
  sh_uint64 candidate_start=0;
  for (sh_uint64 page_index=0;page_index<page_count;page_index++) {
    if (!sh_page_is_allocated(physical_bitmap,page_index)) {
      if (contiguous==0) candidate_start=page_index;
      contiguous++;
      if (contiguous==pages_needed) {
        *pa=candidate_start*SH_PAGE_SIZE;
        return SH_STATUS_SUCCESS;
      }
    } else {
      contiguous=0;
    }
  }
  return SH_STATUS_OUT_OF_MEMORY;
}
SH_STATUS sh_page_alloc_contiguous(sh_page_PAGE_TABLE_POOL *ptp,sh_uint64 size_bytes,sh_page_VIRTUAL_ADRESS *va) {
  if (ptp==SH_NULLPTR || va==SH_NULLPTR || size_bytes==0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pages_needed=size_bytes/SH_PAGE_SIZE;
  if (size_bytes%SH_PAGE_SIZE!=0) pages_needed++;
  sh_page_VIRTUAL_ADRESS candidate_va=0;
  SH_STATUS status=sh_page_search_available_va_range(ptp,0x0,0x00007FFFFFFFF000,pages_needed*SH_PAGE_SIZE,&candidate_va);
  if (status==SH_STATUS_OUT_OF_MEMORY) {
    return SH_STATUS_OUT_OF_MEMORY;
  } else if (status==SH_STATUS_INVALID_INTERNAL_PA) {
    return SH_STATUS_KERNEL_PANIC;
  } else if (status==SH_STATUS_INVALID_PARAMETER) {
    return SH_STATUS_OUT_OF_MEMORY;
  } else if (sh_status_error(status)) {
    return SH_STATUS_OUT_OF_MEMORY;
  }
  sh_page_PHYSICAL_ADRESS candidate_pa=0;
  status=sh_page_search_physical_contiguous_block_na(pages_needed,&candidate_pa);
  if (status!=SH_STATUS_SUCCESS) {
    return SH_STATUS_OUT_OF_MEMORY;
  }
  status=sh_page_map_contiguous_pages_range_ptp(ptp,candidate_va,candidate_pa,SH_PAGE_PRESENT | SH_PAGE_NX | SH_PAGE_RW,pages_needed*SH_PAGE_SIZE);
  if (status!=SH_STATUS_SUCCESS) return status;
  sh_page_set_pages_range_bitmap(physical_bitmap,physical_memory_pages_count,(sh_uint64)candidate_pa/SH_PAGE_SIZE,pages_needed,SH_TRUE);
  *va=candidate_va;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_alloc_contiguous_extended(sh_page_PAGE_TABLE_POOL *ptp,sh_uint64 size_bytes,sh_page_VIRTUAL_ADRESS* va,DEFAULT sh_uint64 flags,DEFAULT sh_page_VIRTUAL_ADRESS va_range_start,DEFAULT sh_uint64 va_range_size_bytes) {
  if (ptp==SH_NULLPTR || va==SH_NULLPTR || size_bytes==0) return SH_STATUS_INVALID_PARAMETER;
  if (flags==SH_DEFVALUE) flags=SH_PAGE_PRESENT | SH_PAGE_NX | SH_PAGE_RW;
  if (va_range_start==SH_DEFVALUE) va_range_start=0x0;
  if (va_range_size_bytes==SH_DEFVALUE) va_range_size_bytes=0x00007FFFFFFFF000;
  if (va_range_start%SH_PAGE_SIZE!=0 || va_range_size_bytes==0 || va_range_size_bytes%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pages_needed=size_bytes/SH_PAGE_SIZE;
  if (size_bytes%SH_PAGE_SIZE!=0) pages_needed++;
  sh_page_VIRTUAL_ADRESS candidate_va=0;
  SH_STATUS status=sh_page_search_available_va_range(ptp,va_range_start,va_range_size_bytes,pages_needed*SH_PAGE_SIZE,&candidate_va);
  if (status==SH_STATUS_OUT_OF_MEMORY) {
    return SH_STATUS_OUT_OF_MEMORY;
  } else if (status==SH_STATUS_INVALID_INTERNAL_PA) {
    return SH_STATUS_KERNEL_PANIC;
  } else if (status==SH_STATUS_INVALID_PARAMETER) {
    return SH_STATUS_OUT_OF_MEMORY;
  } else if (sh_status_error(status)) {
    return SH_STATUS_OUT_OF_MEMORY;
  }
  sh_page_PHYSICAL_ADRESS candidate_pa=0;
  status=sh_page_search_physical_contiguous_block_na(pages_needed,&candidate_pa);
  if (status!=SH_STATUS_SUCCESS) {
    return SH_STATUS_OUT_OF_MEMORY;
  }
  status=sh_page_map_contiguous_pages_range_ptp(ptp,candidate_va,candidate_pa,flags,pages_needed*SH_PAGE_SIZE);
  if (status!=SH_STATUS_SUCCESS) return status;
  sh_page_set_pages_range_bitmap(physical_bitmap,physical_memory_pages_count,(sh_uint64)candidate_pa/SH_PAGE_SIZE,pages_needed,SH_TRUE);
  *va=candidate_va;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_unmap_one_page_ptp(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pml4_i=(va>>39) & 0x1FF;
  sh_uint64 pdpt_i=(va>>30) & 0x1FF;
  sh_uint64 pd_i=(va>>21) & 0x1FF;
  sh_uint64 pt_i=(va>>12) & 0x1FF;
  sh_uint64 *pdpt;
  sh_uint64 *pd;
  sh_uint64 *pt;
  sh_uint64 *pml4=(sh_uint64*)ptp->page_table_va;
  if (!(pml4[pml4_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_ERROR_VA_NOT_MAPPED;
  } else {
    pdpt=sh_page_ptp_pa_to_va(ptp,(pml4[pml4_i] & ~0xFFFULL));
    if (!pdpt) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pdpt[pdpt_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_ERROR_VA_NOT_MAPPED;
  } else {
    pd=sh_page_ptp_pa_to_va(ptp,(pdpt[pdpt_i] & ~0xFFFULL));
    if (!pd) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pd[pd_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_ERROR_VA_NOT_MAPPED;
  } else {
    pt=sh_page_ptp_pa_to_va(ptp,pd[pd_i] & ~0xFFFULL);
    if (!pt) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  pt[pt_i]=0x0ULL;
  __asm__ volatile("invlpg (%0)" :: "r"(va) : "memory");
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_unmap_contiguous_pages_range_ptp(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va,sh_uint64 size_bytes) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0 || size_bytes==0 || size_bytes%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pages=size_bytes/SH_PAGE_SIZE;
  SH_STATUS status=sh_page_is_va_range_mapped_ptp(ptp,va,size_bytes);
  if (status==SH_STATUS_VA_PARTIALLY_MAPPED) return SH_STATUS_ERROR_VA_PARTIALLY_MAPPED;
  if (status==SH_STATUS_VA_NOT_MAPPED) return SH_STATUS_ERROR_VA_NOT_MAPPED;
  for (sh_uint64 i=0;i<pages;i++) {
    status=sh_page_unmap_one_page_ptp(ptp,va+i*SH_PAGE_SIZE);
    if (status!=SH_STATUS_SUCCESS) {
      return status;
    }
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_ptp_va_to_pa(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va,sh_page_PHYSICAL_ADRESS *pa) {
  if (ptp==SH_NULLPTR || pa==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  sh_uint64 pml4_i=(va>>39) & 0x1FF;
  sh_uint64 pdpt_i=(va>>30) & 0x1FF;
  sh_uint64 pd_i=(va>>21) & 0x1FF;
  sh_uint64 pt_i=(va>>12) & 0x1FF;
  sh_uint64 *pdpt;
  sh_uint64 *pd;
  sh_uint64 *pt;
  sh_uint64 *pml4=(sh_uint64*)ptp->page_table_va;
  if (!(pml4[pml4_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_ERROR_VA_NOT_MAPPED;
  } else {
    pdpt=sh_page_ptp_pa_to_va(ptp,(pml4[pml4_i] & ~0xFFFULL));
    if (!pdpt) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pdpt[pdpt_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_ERROR_VA_NOT_MAPPED;
  } else {
    pd=sh_page_ptp_pa_to_va(ptp,(pdpt[pdpt_i] & ~0xFFFULL));
    if (!pd) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pd[pd_i] & SH_PAGE_PRESENT)) {
    return SH_STATUS_ERROR_VA_NOT_MAPPED;
  } else {
    pt=sh_page_ptp_pa_to_va(ptp,pd[pd_i] & ~0xFFFULL);
    if (!pt) return SH_STATUS_INVALID_INTERNAL_PA;
  }
  if (!(pt[pt_i] & SH_PAGE_PRESENT)) return SH_STATUS_ERROR_VA_NOT_MAPPED;
  *pa=pt[pt_i] & 0x000FFFFFFFFFF000;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_unalloc_one_page(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  sh_page_PHYSICAL_ADRESS equivalent_pa;
  SH_STATUS status=sh_page_ptp_va_to_pa(ptp,va,&equivalent_pa);
  if (status!=SH_STATUS_SUCCESS) return status;
  status=sh_page_unmap_one_page_ptp(ptp,va); // If this call return SH_STATUS_ERROR_VA_NOT_MAPPED, there is a severe bug that should cause kernel panic because sh_page_ptp_va_to_pa should already have returned exact same error code.
  if (status!=SH_STATUS_SUCCESS) return status;
  status=sh_page_set_pages_range_bitmap(physical_bitmap,physical_memory_pages_count,(sh_uint64)(equivalent_pa/SH_PAGE_SIZE),1,SH_FALSE);
  if (status!=SH_STATUS_SUCCESS) return status;
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_unalloc_contiguous(sh_page_PAGE_TABLE_POOL *ptp,sh_page_VIRTUAL_ADRESS va,sh_uint64 size_bytes) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  if (va%SH_PAGE_SIZE!=0 || size_bytes==0 || size_bytes%SH_PAGE_SIZE!=0) return SH_STATUS_INVALID_PARAMETER;
  SH_STATUS status=sh_page_is_va_range_mapped_ptp(ptp,va,size_bytes);
  if (status==SH_STATUS_VA_NOT_MAPPED) return SH_STATUS_ERROR_VA_NOT_MAPPED;
  if (status==SH_STATUS_VA_PARTIALLY_MAPPED) return SH_STATUS_ERROR_VA_PARTIALLY_MAPPED;
  sh_uint64 pages=size_bytes/SH_PAGE_SIZE;
  for (sh_uint64 i=0;i<pages;i++) {
    status=sh_page_unalloc_one_page(ptp,va+i*SH_PAGE_SIZE);
    if (status!=SH_STATUS_SUCCESS) {
      return status;
    }
  }
  return SH_STATUS_SUCCESS;
}
SH_STATUS sh_page_analyse_memory_map(sh_page_PAGE_TABLE_POOL *ptp) {
  if (ptp==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  sh_page_MEMORY_MAP_HEADER *memory_map_header=(sh_page_MEMORY_MAP_HEADER *)memory_map_buffer;
  sh_page_MEMORY_MAP_ENTRY *memory_map_cursor=(sh_page_MEMORY_MAP_ENTRY *)(memory_map_buffer+sizeof(sh_page_MEMORY_MAP_HEADER));
  sh_bool verbose=sh_log_get_log_level()==0;
  sh_bool log=sh_log_get_log_level()<=1;
  sh_uint64 highest_usable_segment=0;
  sh_uint64 highest_usable_page=0;
  sh_uint64 biggest_segment_index=0;
  sh_uint64 biggest_segment_pages=0;
  if (log) {
    sh_log_llog("Max pages count is currently set to 0x",SH_LOG_SOURCE_PAGE);
    sh_log_send_uintn_hex(SH_PAGE_MAX_PAGES_COUNT);
    sh_log_send_string(" pages or 0x");
    sh_log_send_uintn_hex(SH_PAGE_MAX_PAGES_COUNT*4096);
    sh_log_send_string(" bytes.\n");
  }
  // first loop : identify memory amount and bigest free region
  for (sh_uint64 i=0;i<memory_map_header->entry_count;i++) {
    sh_uint64 start_page=memory_map_cursor[i].physical_start/4096;
    sh_uint64 end_page=start_page+memory_map_cursor[i].pages_count;
    if (memory_map_cursor[i].type==SH_PAGE_CONVENTIONAL_MEMORY && memory_map_cursor[i].pages_count>biggest_segment_pages) {
      biggest_segment_pages=memory_map_cursor[i].pages_count;
      biggest_segment_index=i;
    }
    if (verbose) {
      sh_log_ldebug("Found memory map segment #",SH_LOG_SOURCE_PAGE);
      sh_log_send_uintn(i);
      sh_log_send_string(": [0x");
      sh_log_send_uintn_hex(start_page*4096);
      sh_log_send_string(" - 0x");
      sh_log_send_uintn_hex(end_page*4096);
      sh_log_send_string("] Memory type: ");
      sh_log_send_uintn(memory_map_cursor[i].type);
      if (memory_map_cursor[i].type==SH_PAGE_CONVENTIONAL_MEMORY) {
        sh_log_send_string(" --> usable\n");
      } else {
        sh_log_send_string(" --> not usable\n");
      }
      if (!(end_page<=SH_PAGE_MAX_PAGES_COUNT)) {
        sh_log_lwarning("Memory map segment #",SH_LOG_SOURCE_PAGE);
        sh_log_send_uintn(i);
        sh_log_send_string(" isn't usable because it overflow over max page count. Enable debug log channel to see more.\n");
      }
    }
    if (memory_map_cursor[i].type==SH_PAGE_CONVENTIONAL_MEMORY && end_page<=SH_PAGE_MAX_PAGES_COUNT) {
      if (end_page>highest_usable_page) {
        highest_usable_segment=i;
        highest_usable_page=end_page;
      }
    }
  }
  physical_memory_pages_count=highest_usable_page;
  physical_memory_bytes_count=physical_memory_pages_count*SH_PAGE_SIZE;
  if (verbose) {
    sh_log_ldebug("Total memory was given by memory map segment #",SH_LOG_SOURCE_PAGE);
    sh_log_send_uintn(highest_usable_segment);
    sh_log_send_string("\n");
    sh_log_ldebug("Total memory (pages): 0x",SH_LOG_SOURCE_PAGE);
    sh_log_send_uintn_hex(physical_memory_pages_count);
    sh_log_send_string(". Total memory (bytes) : 0x");
    sh_log_send_uintn_hex(physical_memory_bytes_count);
    sh_log_send_string("\n");
  }
  if (biggest_segment_pages==0) {
    sh_log_error("No suitable conventional memory segment found.",SH_LOG_SOURCE_PAGE);
    return SH_STATUS_OUT_OF_MEMORY;
  }
  if (memory_map_cursor[biggest_segment_index].pages_count<(physical_memory_pages_count/8)) {
    sh_log_error("Memory is too low or too fragmented to allocate physical bitmap.",SH_LOG_SOURCE_PAGE);
    return SH_STATUS_OUT_OF_MEMORY;
  }
  sh_page_PHYSICAL_ADRESS pa=memory_map_cursor[biggest_segment_index].physical_start;
  sh_page_VIRTUAL_ADRESS va;
  physical_bitmap_size_bytes=physical_memory_pages_count/8;
  if (physical_memory_pages_count%8!=0) physical_bitmap_size_bytes++;
  physical_bitmap_size_pages=physical_bitmap_size_bytes/SH_PAGE_SIZE;
  if (physical_bitmap_size_bytes%SH_PAGE_SIZE!=0) physical_bitmap_size_pages++;
  SH_STATUS status=sh_page_search_available_va_range(ptp,SH_PAGE_KERNEL_PERM_VA_BASE,(SH_PAGE_KERNEL_PERM_VA_END-SH_PAGE_KERNEL_PERM_VA_BASE+1-0x1000),physical_bitmap_size_pages*SH_PAGE_SIZE,&va);
  if (status!=SH_STATUS_SUCCESS) {
    sh_log_error("Memory is too low or too fragmented to allocate physical bitmap.",SH_LOG_SOURCE_PAGE);
    return status;
  }
  status=sh_page_map_contiguous_pages_range_ptp(ptp,va,pa,SH_PAGE_PRESENT | SH_PAGE_NX | SH_PAGE_RW,physical_bitmap_size_pages*SH_PAGE_SIZE);
  if (status==SH_STATUS_OUT_OF_MEMORY) {
    sh_log_error("Memory is too low or too fragmented to allocate physical bitmap.",SH_LOG_SOURCE_PAGE);
    return status;
  } else if (status!=SH_STATUS_SUCCESS) {
    sh_log_error("An unknow error happened during physical bitmap pages mapping. See error below",SH_LOG_SOURCE_PAGE);
    return status;
  }
  physical_bitmap=(sh_uint8*)va;
  status=sh_mem_set_8(physical_bitmap,0xFF,physical_bitmap_size_bytes);
  if (sh_status_error(status)) {
    sh_log_error("An unknow error happened during physical bitmap filling with 0xFF. See error below.",SH_LOG_SOURCE_PAGE);
    return status;
  }
  // second loop : actually set all free regions into physical bitmap
  for (sh_uint64 i=0;i<memory_map_header->entry_count;i++) {
    sh_uint64 start_page=memory_map_cursor[i].physical_start/4096;
    sh_uint64 end_page=start_page+memory_map_cursor[i].pages_count;
    if (end_page<=SH_PAGE_MAX_PAGES_COUNT) {
      if (memory_map_cursor[i].type==SH_PAGE_CONVENTIONAL_MEMORY) {
        SH_STATUS status=sh_page_set_pages_range_bitmap(physical_bitmap,physical_memory_pages_count,memory_map_cursor[i].physical_start/SH_PAGE_SIZE,memory_map_cursor[i].pages_count,SH_FALSE);
        if (sh_status_error(status)) {
          sh_log_error("Couldn't set this memory map segment to usable.",SH_LOG_SOURCE_PAGE);
          return SH_STATUS_PMAP_NO_PAGES_SET;
        }
      }
    }
  }
  sh_page_set_pages_range_bitmap(physical_bitmap,physical_memory_pages_count,pa/SH_PAGE_SIZE,physical_bitmap_size_pages,SH_TRUE);
  return SH_STATUS_SUCCESS;
}
sh_page_VIRTUAL_ADRESS sh_page_get_physical_bitmap_ptr() {
  return (sh_page_VIRTUAL_ADRESS)physical_bitmap;
}
static sh_uint64 popcount64(sh_uint64 x) {
  x=x-((x>>1) & 0x5555555555555555ULL);
  x=(x & 0x3333333333333333ULL)+((x>>2) & 0x3333333333333333ULL);
  x=(x+(x>>4)) & 0x0F0F0F0F0F0F0F0FULL;
  x=x+(x>>8);
  x=x+(x>>16);
  x=x+(x>>32);
  return x & 0x7F;
}
SH_STATUS sh_page_get_memory_stats(sh_page_MEM_STATS *mem_stats) {
  if (mem_stats==SH_NULLPTR) return SH_STATUS_INVALID_PARAMETER;
  mem_stats->memory_total_pages=physical_memory_pages_count;
  mem_stats->memory_total_bytes=physical_memory_bytes_count;
  sh_uint64 free_pages=0;
  sh_uint64 used_pages=0;
  sh_uint64 largest_free_block=0;
  sh_uint64 largest_used_block=0;
  sh_uint64 free_blocks_count=0;
  sh_uint64 used_blocks_count=0;
  sh_uint64 current_free_block=0;
  sh_uint64 current_used_block=0;
  sh_uint64 full_uint64_count=physical_memory_pages_count/64;
  sh_uint64 remaining_bits=physical_memory_pages_count%64;
  sh_uint64 *bitmap64=(sh_uint64*)physical_bitmap;
  for (sh_uint64 i=0;i<full_uint64_count;i++) {
    sh_uint64 val=bitmap64[i];
    unsigned ones=popcount64(val);
    unsigned zeros=64-ones;
    used_pages+=ones;
    free_pages+=zeros;
    for (int b=0;b<64;b++) {
      sh_bool bit_set=(val>>b) & 1;
      if (bit_set) {
        current_used_block++;
        if (current_free_block) {
          free_blocks_count++;
          if (current_free_block>largest_free_block) {
            largest_free_block=current_free_block;
          }
          current_free_block=0;
        }
      } else {
        current_free_block++;
        if (current_used_block) {
          used_blocks_count++;
          if (current_used_block>largest_used_block) {
            largest_used_block=current_used_block;
          }
          current_used_block=0;
        }
      }
    }
  }
  if (remaining_bits) {
    sh_uint64 val=bitmap64[full_uint64_count] & ((1ULL<<remaining_bits)-1);
    unsigned ones=popcount64(val);
    unsigned zeros=remaining_bits-ones;
    used_pages+=ones;
    free_pages+=zeros;
    for (sh_uint64 b=0;b<remaining_bits;b++) {
      sh_bool bit_set=(val>>b) & 1;
      if (bit_set) {
        current_used_block++;
        if (current_free_block) {
          free_blocks_count++;
          if (current_free_block>largest_free_block) {
            largest_free_block=current_free_block;
          }
          current_free_block=0;
        }
      } else {
        current_free_block++;
        if (current_used_block) {
          used_blocks_count++;
          if (current_used_block>largest_used_block) {
            largest_used_block=current_used_block;
          }
          current_used_block=0;
        }
      }
    }
  }
  if (current_free_block) {
    free_blocks_count++;
    if (current_free_block>largest_free_block) {
      largest_free_block=current_free_block;
    }
  }
  if (current_used_block) {
    used_blocks_count++;
    if (current_used_block>largest_used_block) {
      largest_used_block=current_used_block;
    }
  }
  mem_stats->free_pages=free_pages;
  mem_stats->used_pages=used_pages;
  mem_stats->free_ratio=(double)free_pages/(double)physical_memory_pages_count;
  mem_stats->used_ratio=(double)used_pages/(double)physical_memory_pages_count;
  mem_stats->largest_free_block=largest_free_block;
  mem_stats->largest_used_block=largest_used_block;
  mem_stats->free_blocks_count=free_blocks_count;
  mem_stats->used_blocks_count=used_blocks_count;
  mem_stats->physical_bitmap_size_bytes=(physical_memory_pages_count+7)/8;
  mem_stats->physical_bitmap_size_pages=(mem_stats->physical_bitmap_size_bytes+4095)/4096;
  return SH_STATUS_SUCCESS;
}
