#include "assert.H"
#include "exceptions.H"
#include "console.H"
#include "paging_low.H"
#include "page_table.H"

PageTable * PageTable::current_page_table = NULL;
unsigned int PageTable::paging_enabled = 0;
ContFramePool * PageTable::kernel_mem_pool = NULL;
ContFramePool * PageTable::process_mem_pool = NULL;
unsigned long PageTable::shared_size = 0;



void PageTable::init_paging(ContFramePool * _kernel_mem_pool,
                            ContFramePool * _process_mem_pool,
                            const unsigned long _shared_size)
{
  kernel_mem_pool = _kernel_mem_pool;
  process_mem_pool = process_mem_pool;
  shared_size = _shared_size;
  
  Console::puts("\nInitialized Paging System\n");
}

PageTable::PageTable()
{
  unsigned long * temp_page_directory = (unsigned long *) ((kernel_mem_pool->get_frames(1)) << 12); // Getting frame address to assign to page directory. 
 
  unsigned long * page_table = (unsigned long *) ((kernel_mem_pool->get_frames(1)) << 12); // Get another frame for the page table.

  unsigned long tempaddr = 0; // Temporary address iterator.

  // We need to map the first 4MB.
  // This first for loop was here in case I needed to set the first 2mb as not present.
  for(unsigned int i = 0; i < 512; i++)
  {
    page_table[i] = tempaddr | 3; // set bits 0 and 1 to present and read/write respectively
    tempaddr += 4096; // 4096 = 4kb
  }
  
  for(unsigned int i = 512; i < 1024; i++)
  {
    page_table[i] = tempaddr | 3; // set bits 0 and 1 to present and read/write respectively
    tempaddr += 4096; // 4096 = 4kb
  }
  

  // Fill first entry of page directory.
  temp_page_directory[0] = (unsigned long)page_table;
  temp_page_directory[0] = temp_page_directory[0] | 3;  

  for(unsigned int j = 1; j < 1024; j++)
  {
    temp_page_directory[j] = 0 | 2;
  }

  page_directory = temp_page_directory;

  current_page_table = this;

  Console::puts("\nConstructed Page Table object\n");
}


void PageTable::load()
{
  write_cr3((unsigned long)page_directory);
  Console::puti((unsigned long)page_directory);
  current_page_table = this;
  Console::puts("\nLoaded page table\n");
}

void PageTable::enable_paging()
{
  write_cr3((unsigned long)current_page_table->page_directory);
  write_cr0(read_cr0() | 0x80000000);
  paging_enabled = read_cr0();
  Console::puts("\nEnabled paging\n");
}

void PageTable::handle_fault(REGS * _r)
{

  // Initializing some variables to make things easier.
  unsigned long * temp_pd = (unsigned long *) (current_page_table->page_directory);
  unsigned long * temp_pt = (unsigned long *) (*temp_pd);

  unsigned long pt_index = (read_cr2() >> 12) & 0x3FFFFF;
  unsigned long pd_index = read_cr2() >> 22;

  // If there isn't a page table present.
  if(!(temp_pd[pd_index] & 1))
  {
    Console::puts("\nPage Table not Present.\n");
    
    unsigned long * new_pt = (unsigned long *) (kernel_mem_pool->get_frames(1) << 12);
    
    // Set new page table entries as present.
    // Start at beginning address for the page table
    // this should be pd_index << 22.
    unsigned long temp_addr = pd_index << 22;
    for(unsigned int i = 0; i < 1024; i++)
    {
      new_pt[i] = temp_addr | 3;
      temp_addr += 4096;
    }

    // Add page table to page directory.
    temp_pd[pd_index] = (unsigned long) new_pt;
    temp_pd[pd_index] = temp_pd[pd_index] | 3;
  }

  /*
  // Why is this commented out?

  // Unfortunately, for whatever reason, any time I try to call
  // process_mem_pool->get_frames() the entire program crashes.
  // I've tried evaluating lines immediately afterwards, adding
  // for(;;);, the program ignores this and continues to crash.
  // Since I have no idea why this is happening, I can only assume
  // I did something wrong in cont_frame_pool or that this is just
  // an issue I didn't address properly somewhere and didn't see
  // addressed in the instructions. Regardless, I left what I believe
  // would be the implementation of handle_fault in the case that I
  // needed to allocate a frame in process_mem_pool in order to
  // allocate a page for a process commented out so I could at least
  // show that I knew how it would be implemented.

  if(read_cr2() >> 22 > 0)
  {
    if(((unsigned long *)temp_pd[pd_index])[pt_index] & 1)
    {
      Console::puts("\nPage not present.\n");
      unsigned long * new_page = (unsigned long *) (process_mem_pool->get_frames(1) << 12);
    
      // Set new page as page table entry.
      ((unsigned long *)temp_pd[pd_index])[pt_index] = (unsigned long) new_page | 3;
    }
  }
  */

  Console::puts("\nhandled page fault\n");
}

