#include "paging.h"
#include "libc/stdio.h"

/// Use a bitmap to keep track of allocated pages
char pages_status[PAGING_MAX_PAGES/8];
/// Kernel page directory (ATTENTION need to be 4096)
u32 k_pd[PAGING_MAX_DIR_ENTRY] __attribute__((aligned(4096)));
/// Kernel page table
u32 k_pt[PAGING_MAX_DIR_ENTRY][1024] __attribute__((aligned(4096)));

void paging_enable(){
    // Init pages status
    for(int i=0;i<PAGING_MAX_PAGES/8;i++)
        pages_status[i]=0;

    // Init page directory
    for(int i=0;i<PAGING_MAX_DIR_ENTRY;i++){
        k_pd[i]=0;
    }
    k_pd[0]=((int)&k_pt[0][0]);
    k_pd[0]|=7; // Permissions

    // Init page table 0
    int addr_offset=0;
    for(int i=0;i<1024;i++){
        k_pt[0][i]=addr_offset;
        k_pt[0][i]|=7; // Permission
        paging_set_usage(addr_offset,1); // Mark addr as used
        addr_offset+=PAGING_PAGE_SIZE; // 4Ko pages
    }

    // Allow access to more ram
    for(int i=1;i<PAGING_MAX_DIR_ENTRY;i++){
        k_pd[i]=((int)&k_pt[i][0]);
        k_pd[i]|=7; // Permissions
        for(int j=0;j<1024;j++){
            k_pt[i][j]=addr_offset;
            k_pt[i][j]|=7; // Permission
            addr_offset+=PAGING_PAGE_SIZE; // 4Ko pages
        }
    }

    // Turns on paging
    asm(
        "movl %0, %%eax       \n\t"
        "movl %%eax, %%cr3    \n\t" // Configure page table location
        "movl %%cr0, %%eax    \n\t"
        "orl %1, %%eax        \n\t"
        "movl %%eax, %%cr0    \n\t" // Turn on paging
        :: "b" (k_pd), "i" (PAGING_CR0_BIT)
    );
}
void paging_set_usage(int addr,char state){
    char bytes=pages_status[addr/PAGING_PAGE_SIZE/8];
    char bit=addr/PAGING_PAGE_SIZE%8;
    if(state=0)
        pages_status[addr/PAGING_PAGE_SIZE/8]=~(1<<bit)&bytes;
    else
        pages_status[addr/PAGING_PAGE_SIZE/8]=(1<<bit)|bytes;
}

void paging_dump(int min,int max){
    for(int i=0;i<(PAGING_MAX_PAGES/8);i++){
        for(int j=0;j<8;j++){
            char status=(pages_status[i]>>j)&0x1;
            if((i*8+j)>=min){
                if((i*8+j)<max || max<0)
                    printi(status);
            }
        }
    }
}

char* paging_allocate_next_page(){
    for(int i=0;i<(PAGING_MAX_PAGES/8);i++){
        char bytes=pages_status[i];
        for(int j=0;j<8;j++){
            char state=(bytes>>j)&1;
            if(state!=1){
                int page_id=i*8+j;
                int page_addr=PAGING_PAGE_SIZE*page_id;
                paging_set_usage(page_addr,1);
                return((char*)page_addr);
            }
        }
    }
    print("Could not allocate anymore pages! Stopping...");
    asm("hlt");
}

// TODO: Take p into account
int *paging_allocate(int p){
    // ----- Allow kernel access during interruption
    int *page_dir=(int*)paging_allocate_next_page();
    int *k_page_table=(int*)paging_allocate_next_page();
    // Init page directory
    page_dir[0]=(int)k_page_table|3;
    // Init page table 0
    int addr_offset=0;
    for(int i=0;i<1024;i++){
        k_page_table[i]=addr_offset;
        k_page_table[i]|=3; // Permission
        addr_offset+=PAGING_PAGE_SIZE; // 4Ko pages
    }
  
    // ----- Task table
    int *u_page_table=(int*)paging_allocate_next_page();
    page_dir[1]=(int)u_page_table|7; // 1024*1024*4096/4
    u_page_table[0]=(int)page_dir|7;
    u_page_table[1]=(int)k_page_table|7;
    u_page_table[2]=(int)u_page_table|7;
    u_page_table[3]=(int)paging_allocate_next_page()|7;

    return page_dir;
}

void paging_page_fault(){
  print("Page fault!");
  asm("hlt");
}