// implement fork from user space

#include <inc/string.h>
#include <inc/lib.h>

// PTE_COW marks copy-on-write page table entries.
// It is one of the bits explicitly allocated to user processes (PTE_AVAIL).
#define PTE_COW		0x800
#define WRITE_ERROR 	0x002
//
// Custom page fault handler - if faulting page is copy-on-write,
// map in our own private writable copy.
//
static void
pgfault(struct UTrapframe *utf)
{
	void *addr = (void *) utf->utf_fault_va;
	uint32_t err = utf->utf_err;
	int r, i;

	// Check that the faulting access was (1) a write, and (2) to a
	// copy-on-write page.  If not, panic.
	// Hint:
	//   Use the read-only page table mappings at vpt
	//   (see <inc/memlayout.h>).

	// LAB 4: Your code here.

	//cprintf("pgfault: here is the faulting address: %x  and eip %x \n", (uint32_t)addr, utf->utf_eip);

	if ((err & WRITE_ERROR) == 0) {
		panic("fork.c:pgfault: this is not a write error\n");
	}
	
	if  ((vpt[(uint32_t)addr / PGSIZE] & PTE_COW) == 0)    {
		cprintf("faulty addr is %x\n", addr);
		panic("fork.c:pgfault: the page is not copy on write \n");
	}
	//cprintf("pgfault checks done\n");	

	// Allocate a new page, map it at a temporary location (PFTEMP),
	// copy the data from the old page to the new page, then move the new
	// page to the old page's address.
	// Hint:
	//   You should make three system calls.
	//   No need to explicitly delete the old page's mapping.
	
	// LAB 4: Your code here.
	
	envid_t cur_id = sys_getenvid();
	if (cur_id < 0) {
		panic("problem with computing id\n");
	}

	//cprintf("pgfault: check 3 \n");

	if (sys_page_alloc(cur_id, PFTEMP, PTE_W|PTE_P|PTE_U) < 0) {
		panic("fork.c:pgfault: allocate a new page for copy-on-write failed\n");
	}

	//cprintf("pgfault: check 4 \n");
	
	uint32_t new_addr = ((uint32_t)addr / PGSIZE) * PGSIZE;
	if ((vpt[new_addr/PGSIZE] & (PTE_U|PTE_P)) == (PTE_U | PTE_P)) {
		cprintf("faulting address has proper permissions\n");
		for (i = 0; i < PGSIZE/4; i++) {
			((uint32_t *)PFTEMP)[i] = ((uint32_t *)new_addr)[i];
		} 
	}
	else {
		cprintf("pgfault: error: the page is not mapped for user or does not exist\n");
	}
	//cprintf("pgfault: check 5 \n");
	
	
	
	sys_page_map(cur_id, PFTEMP, cur_id, (void *)(((uint32_t)addr/PGSIZE)*PGSIZE), PTE_W|PTE_P|PTE_U);
}

//
// Map our virtual page pn (address pn*PGSIZE) into the target envid
// at the same virtual address.  If the page is writable or copy-on-write,
// the new mapping must be created copy-on-write, and then our mapping must be
// marked copy-on-write as well.  (Exercise: Why mark ours copy-on-write again
// if it was already copy-on-write?)
//
// Returns: 0 on success, < 0 on error.
// It is also OK to panic on error.
// 
/* static int */
/* duppage(envid_t envid, unsigned pn) */
/* { */
/* 	int r; */
/* 	void *addr; */
/* 	pte_t pte; */
	
/* 	//cprintf("check 1   \n"); */
/* 	// LAB 4: Your code here. */
/* 	envid_t parent = sys_getenvid(); */
/* 	if (parent < 0) { */
/* 		panic("problem with id of parent\n"); */
/* 	} */
	
/* 	//check if page table for this page # exists first */
/* 	uint32_t test_addr = (uint32_t)vpt + 4*pn; */
/* 	//cprintf("check 2, test addrs: %x   \n", test_addr); */
/* 	if (  (vpd[PTX(test_addr)] & PTE_P) == 0 ) { */
		
/* 		return 0; //do not map anywhere */
/* 	} */

	

/* 	if ( (vpt[pn] & PTE_P) == PTE_P) { */
/* 		//cprintf("duppage c \n"); */
/* 		if (  ((vpt[pn] & PTE_W) == PTE_W) ||( (vpt[pn] & PTE_COW) == PTE_COW)    ) {	 */
			
/* 			if (sys_page_map(parent, (void *)(pn*PGSIZE), envid, (void *) (pn*PGSIZE), (vpt[pn] & (~PTE_W) & (PTE_U | PTE_AVAIL | PTE_P)) |  PTE_COW) < 0) { */
/* 				cprintf("problem with sys_page_map, here is vpt[pn] %x and pn %d \n", vpt[pn], pn); */
/* 				panic("sys_page_maps"); */
/* 			} */
				

/* 			if (sys_page_map(parent,(void *)(pn*PGSIZE), parent, (void *)(pn*PGSIZE), (vpt[pn] & (~PTE_W) & (PTE_U | PTE_AVAIL | PTE_P | PTE_W)) |  PTE_COW) < 0) { */
/* 				panic("problem with sys_page_map in the parent\n"); */
/* 			} */
			
/* 		} else { */
			
/* 			if (sys_page_map(parent, (void *)(pn*PGSIZE), envid, (void *)(pn*PGSIZE), vpt[pn] & (PTE_U | PTE_P | PTE_W | PTE_AVAIL))) { */
/* 				panic("sys_page_map"); */
/* 			} */
			
	
/* 		} */

/* 	} else { */
/* 		//do nothing if we do not have these permissions */
/* 		return 0; */
/* 	} */
/* 	//cprintf("duppage b \n"); */
/* 	//cprintf("check 4   \n"); */
/* 	return 0; */
/* } */


void
duppage2(envid_t dstenv, void *addr)
{
	int r;
//+	
	//check if page table is present
	if ((vpd[PDX((uint32_t)addr)] & PTE_P) == 0) {
		return;
	}
	//check if page is mapped
	if ((vpt[(uint32_t)addr/PGSIZE] & PTE_P) == 0) {
		return;
	}

	if ((vpt[(uint32_t)addr/PGSIZE] & PTE_SHARE) == PTE_SHARE) {
		if ((r = sys_page_map(0, addr, dstenv, addr, PTE_P|PTE_U|PTE_W|PTE_SHARE)) < 0) {
			panic("sys_page_map: %e", r);
		}
		return;	
	}
//-
	if ((r = sys_page_alloc(dstenv, addr, PTE_P|PTE_U|PTE_W)) < 0) {
		panic("sys_page_alloc: %e", r);	
	}
	if ((r = sys_page_map(dstenv, addr, 0, UTEMP, PTE_P|PTE_U|PTE_W)) < 0) {
		panic("sys_page_map: %e", r);
	}
	memmove(UTEMP, addr, PGSIZE);
	if ((r = sys_page_unmap(0, UTEMP)) < 0)
		panic("sys_page_unmap: %e", r);
	


} 

envid_t
dumbfork(void)
{
	envid_t envid;
	uint8_t *addr;
	int r;
	extern unsigned char end[];

	// Allocate a new child environment.
	// The kernel will initialize it with a copy of our register state,
	// so that the child will appear to have called sys_exofork() too -
	// except that in the child, this "fake" call to sys_exofork()
	// will return 0 instead of the envid of the child.
	envid = sys_exofork();
	if (envid < 0)
		panic("sys_exofork: %e", envid);
	if (envid == 0) {
		// We're the child.
		// The copied value of the global variable 'env'
		// is no longer valid (it refers to the parent!).
		// Fix it and return 0.
		env = &envs[ENVX(sys_getenvid())];
		return 0;
	}

	// We're the parent.
	// Eagerly copy our entire address space into the child.
	// This is NOT what you should do in your fork implementation.
	for (addr = (uint8_t*) UTEXT; addr < (uint8_t*)USTACKTOP; addr += PGSIZE)
		duppage2(envid, addr);

	// Also copy the stack we are currently running on.
	duppage2(envid, ROUNDDOWN(&addr, PGSIZE));

	// Start the child environment running
	if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
		panic("sys_env_set_status: %e", r);

	return envid;
}


static int
duppage(envid_t envid, unsigned pn)
{
	int r;
	void *addr;
	pte_t pte;

	// LAB 4: Your code here.
	addr = (void *) (pn*PGSIZE);
    pte = vpt[pn];
    
    if ((vpt[(uint32_t)addr/PGSIZE] & PTE_SHARE) == PTE_SHARE) {
		if ((r = sys_page_map(0, addr, envid, addr, PTE_P|PTE_U|PTE_W|PTE_SHARE)) < 0) {
			panic("sys_page_map: %e", r);
		}
		return 0;	
	}

    if ((pte&(PTE_COW|PTE_W)) != 0) {
        r = sys_page_map(0, addr, envid, addr, PTE_U|PTE_COW|PTE_P);
        if (r < 0)
            return r;
        //cprintf("1 mapping addr cow = %08x\n",addr);
        r = sys_page_map(0, addr, 0, addr, PTE_U|PTE_COW|PTE_P);
        //cprintf("p4\n");
        if (r < 0)
            return r;
        //cprintf("mapping addr cow = %08x\n",addr);
        
    } else {
        //cprintf("mapping addr read only = %08x\n",addr);
        r = sys_page_map(0, addr, envid, addr, PTE_U|PTE_P);
        if (r < 0)
            return r;
    }
	return 0;
}



//
// User-level fork with copy-on-write.
// Set up our page fault handler appropriately.
// Create a child.
// Copy our address space and page fault handler setup to the child.
// Then mark the child as runnable and return.
//
// Returns: child's envid to the parent, 0 to the child, < 0 on error.
// It is also OK to panic on error.
//
// Hint:
//   Use vpd, vpt, and duppage.
//   Remember to fix "env" and the user exception stack in the child process.
//   Neither user exception stack should ever be marked copy-on-write,
//   so you must allocate a new page for the child's user exception stack.
//
envid_t
fork(void)
{ 
	
	//return dumbfork();

    int r;
    set_pgfault_handler(&pgfault);
	// LAB 4: Your code here.
	envid_t new_env = sys_exofork();
    
    if (new_env < 0)
        panic("Could not create new environment");

    //child process
    if (new_env == 0){
        env = &envs[ENVX(sys_getenvid())];
        return 0;
}

    //copy mappings
    int i,j,pn;
    pde_t pde;
    pte_t pte;
    for(i=(UTEXT>>PDXSHIFT); i < (UTOP>>PDXSHIFT); i++){
        pde = vpd[i];
        if ((pde&PTE_P)!= 0){
            for (j = 0; j < NPTENTRIES; j++){
                pn = (i<<(PDXSHIFT-PTXSHIFT)) + j;
                pte = vpt[pn];
                if (((pte&PTE_P)!=0) && (pn != ((UXSTACKTOP-PGSIZE) >> PTXSHIFT))) {
                    r = duppage(new_env,pn);
                    if (r < 0)
                        panic("could not copy mapping %d from parent to child: %e",i,r);
                }
            }
        }
    }

    //allocate user exception stack
    r = sys_page_alloc(new_env, ((void *) (UXSTACKTOP-PGSIZE)), PTE_P|PTE_U|PTE_W);
    if (r < 0)
        panic("could not allocate page for user exception stack: %e",r);
    //cprintf("page fault handler: %08x\n",&pgfault);
    sys_env_set_pgfault_upcall(new_env, env->env_pgfault_upcall);
    sys_env_set_status(new_env, ENV_RUNNABLE);
    
	return new_env;	
		

}

// Challenge!
int
sfork(void)
{
	panic("sfork not implemented");
	return -E_INVAL;
}
