#include <printf.h>
#include <paging.h>
#include <stdint.h>

// for now, this will always remain the same. 
// If this ever isn't the case, we'll add paramaters to the paging functions.
// We will also never need to mess with many bits, 
// as we are not implimenting security for a bare metal fractal generator.
// Also, is this really the cleanest way to do things?

//after we get paging working, we can probably remove these structs and replace them with plain old uint_16ts.

void debug_print_memory() {
  struct memory_table *memtable = (struct memory_table *)&_meminfo_loc; 
  printf(" __________________________________________________________________________\n");
  printf("| type\tstart\t\t\tend\t\t\tsize\t\t   |\n");
  printf("|--------------------------------------------------------------------------|\n");
  for(unsigned int i = 0; memtable[i].length > 0; i++) {
    printf("| %u %u\t0x%p\t0x%p\t0x%p |\n", memtable[i].type, memtable[i].ACPI, memtable[i].base, (memtable[i].base + memtable[i].length), memtable[i].length);
  }
  printf("----------------------------------------------------------------------------\n");
}


/**
 * You can critisise the quality of this function all you want, it's messy due to planning mistakes
 * and I'm planning on sanatising it. 
 * BUT, don't critisise it _just_ for using goto, that's bullshit. See the following links.
 *    
 *    https://www.kernel.org/doc/html/v4.17/process/coding-style.html "goto" section
 *    https://koblents.com/Ches/Links/Month-Mar-2013/20-Using-Goto-in-Linux-Kernel-Code/
**/

bool map_page(uintptr_t virtual_addr, uintptr_t physical_addr, uint8_t size) {
  printf("map page called\n");
  if((virtual_addr % (1 << size)) || (physical_addr % (1 << size))) {
    return 0; 
  }
  page_table *table = (page_table *)PAGEMAP_LOCATION;
  int pte_i = (virtual_addr >> 12) & 0x1ff;
  int pde_i = (virtual_addr >> 21) & 0x1ff;
  int pdpe_i = (virtual_addr >> 30) & 0x1ff;
  int pml4e_i = (virtual_addr >> 39) & 0x1ff;
  //TODO remove this debugging info
  printf("Virtual offsets:\npte:\t\t%i\npde:\t\t%i\npdpe:\t\t%i\npml4e\t\t%i\n", pte_i, pde_i, pdpe_i, pml4e_i);

  if(table->pml4e[pml4e_i].present) {
    if(table->pml4e[pml4e_i].base_ptr != (uintptr_t)&table->pdpe[pdpe_i] >> 12) goto error;
    if(table->pdpe[pdpe_i].present) {
      if(size == PAGE_SIZE_1G) {
        if(table->pdpe[pdpe_i].base_ptr == (uintptr_t)physical_addr >> 30 & 0x1ff)
          return true;
        goto error;
      }
      if(table->pdpe[pdpe_i].base_ptr != (uintptr_t)&table->pde[pde_i] >> 12) goto error;

      if(table->pde[pde_i].present) { 
        if(size == PAGE_SIZE_2M) {
          if(table->pde[pde_i].base_ptr == (uintptr_t)physical_addr >> 21 & 0x1ff)
            return true;
          goto error;
        }
        if(table->pde[pde_i].base_ptr != (uintptr_t)&table->pte[pte_i] >> 12) goto error;
        if(table->pte[pte_i].present) {
          if(table->pte[pte_i].base_ptr != ((physical_addr >> 12) & 0x1ff)) goto error;
          return true;
        }
        else goto mod_page_pte;
      }
      else goto mod_page_pde;
    }
    else goto mod_page_pdpe;
  }
  else {
    table->pml4e[pml4e_i].base_ptr = (uintptr_t)&table->pdpe[pdpe_i] >> 12;
    table->pdpe[pml4e_i].read_write = 1;
    table->pml4e[pml4e_i].present = 1;
mod_page_pdpe:
    table->pdpe[pdpe_i].read_write = 1;
    if(size == PAGE_SIZE_1G) {
      table->pdpe[pdpe_i].size = 1;
      table->pdpe[pdpe_i].base_ptr = physical_addr >> 30;
      table->pdpe[pdpe_i].present = 1;
      return true;
    }
    table->pdpe[pdpe_i].base_ptr = (uintptr_t)&table->pde[pde_i] >> 12;
    table->pdpe[pdpe_i].present = 1;
mod_page_pde:
    table->pde[pde_i].read_write = 1;
    if(size == PAGE_SIZE_2M) {
      table->pde[pde_i].size = 1;
      table->pde[pde_i].base_ptr = physical_addr >> 21;
      table->pde[pde_i].present = 1;
      return true;
    }
    table->pde[pde_i].base_ptr = (uintptr_t)&table->pte[pte_i] >> 12;
    table->pde[pde_i].present = 1;
mod_page_pte:
    table->pte[pte_i].base_ptr = (physical_addr >> 12);
    table->pte[pte_i].read_write = 1;
    table->pte[pte_i].present = 1;
    return true;
  }
error:
  printf("Page allocation error!\n");
  return false;
}
