/* See COPYRIGHT for copyright information. */

#include <inc/x86.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>

#include <kern/env.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/syscall.h>
#include <kern/console.h>
#include <kern/sched.h>



// Print a string to the system console.
// The string is exactly 'len' characters long.
// Destroys the environment on memory errors.
static void
sys_cputs(const char *s, size_t len)
{
	
	// Check that the user has permission to read memory [s, s+len).
	// Destroy the environment if not.
	// LAB 3: Your code here.
	user_mem_assert(curenv, s, len, PTE_U);
	

	// Print the string supplied by the user.
	cprintf("%.*s", len, s);
	return;
}

// Read a character from the system console.
// Returns the character.
static int
sys_cgetc(void) 
{
	int c;

	// The cons_getc() primitive doesn't wait for a character,
	// but the sys_cgetc() system call does.
	while ((c = cons_getc()) == 0)
		/* do nothing */;

	return c;
}

// Returns the current environment's envid.
static envid_t
sys_getenvid(void)
{
	
	return curenv->env_id;
}

// Destroy a given environment (possibly the currently running environment).
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
static int
sys_env_destroy(envid_t envid)
{
	int r;
	struct Env *e;

	if ((r = envid2env(envid, &e, 1)) < 0)
		return r;
	if (e == curenv)
		cprintf("[%08x] exiting gracefully\n", curenv->env_id);
	else
		cprintf("[%08x] destroying %08x\n", curenv->env_id, e->env_id);
	env_destroy(e);
	return 0;
}

// Deschedule current environment and pick a different one to run.
static void
sys_yield(void)
{
	sched_yield();
}

// Allocate a new environment.
// Returns envid of new environment, or < 0 on error.  Errors are:
//	-E_NO_FREE_ENV if no free environment is available.
static envid_t
sys_exofork(void)
{
	// Create the new environment with env_alloc(), from kern/env.c.
	// It should be left as env_alloc created it, except that
	// status is set to ENV_NOT_RUNNABLE, and the register set is copied
	// from the current environment -- but tweaked so sys_exofork
	// will appear to return 0.
	
	// LAB 4: Your code here.

	struct Env * new_env;
	//cprintf("sys_exofork: entering exofork\n");
	if (env_alloc(&new_env, curenv->env_id) < 0) {
		return -E_NO_FREE_ENV;
	}
	//cprintf("sys_exofork: created successfully with id %08x \n", new_env->env_id);
	new_env->env_status = ENV_NOT_RUNNABLE;
	new_env->env_tf = curenv->env_tf;
	new_env->env_tf.tf_regs.reg_eax = 0;
	new_env->env_parent_id=curenv->env_id;
	//cprintf("sys_exofork: before returning id is %08x \n", new_env->env_id);
	return new_env->env_id;
	
}

// Set envid's env_status to status, which must be ENV_RUNNABLE
// or ENV_NOT_RUNNABLE.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if status is not a valid status for an environment.
static int
sys_env_set_status(envid_t envid, int status)
{
  	// Hint: Use the 'envid2env' function from kern/env.c to translate an
  	// envid to a struct Env.
	// You should set envid2env's third argument to 1, which will
	// check whether the current environment has permission to set
	// envid's status.
	
	// LAB 4: Your code here.
	struct Env * checkEnv;

	//check if status is fine
	if ((status != ENV_RUNNABLE )&&( status != ENV_NOT_RUNNABLE)) {
		return -E_INVAL;
	}
	//check permissions
	if (envid2env(envid, &checkEnv, 1)<0) {
		return -E_BAD_ENV;
	}
	checkEnv->env_status = status;
	return 0;

	
}

// Set envid's trap frame to 'tf'.
// tf is modified to make sure that user environments always run at code
// protection level 3 (CPL 3) with interrupts enabled.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
static int
sys_env_set_trapframe(envid_t envid, struct Trapframe *tf)
{
	// LAB 4: Your code here.
	// Remember to check whether the user has supplied us with a good
	// address!
	struct Env * checkEnv;

	if (envid2env(envid, &checkEnv, 1)<0) {
		return -E_BAD_ENV;
	}

	tf->tf_cs = tf->tf_cs | 0x03;
	tf->tf_eflags |=FL_IF;

	checkEnv->env_tf = *tf;

	return 0;
	
}

// Set the page fault upcall for 'envid' by modifying the corresponding struct
// Env's 'env_pgfault_upcall' field.  When 'envid' causes a page fault, the
// kernel will push a fault record onto the exception stack, then branch to
// 'func'.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
static int
sys_env_set_pgfault_upcall(envid_t envid, void *func)
{

	
	// LAB 4: Your code here.
	struct Env * checkEnv;
	//cprintf("entering sys_env_set_pgfault_upcall\n");
	//check permissions and if the environment is valid
	if (envid2env(envid, &checkEnv, 1)<0) {
		cprintf("bad env in sys_env_set_pgfault_upcall\n");
		return -E_BAD_ENV;
	}
	
	checkEnv->env_pgfault_upcall = func;
	return 0;
}

// Allocate a page of memory and map it at 'va' with permission
// 'perm' in the address space of 'envid'.
// The page's contents are set to 0.
// If a page is already mapped at 'va', that page is unmapped as a
// side effect.
//
// perm -- PTE_U | PTE_P must be set, PTE_AVAIL | PTE_W may or may not be set,
//         but no other bits may be set.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if va >= UTOP, or va is not page-aligned.
//	-E_INVAL if perm is inappropriate (see above).
//	-E_NO_MEM if there's no memory to allocate the new page,
//		or to allocate any necessary page tables.
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
	// Hint: This function is a wrapper around page_alloc() and
	//   page_insert() from kern/pmap.c.
	//   Most of the new code you write should be to check the
	//   parameters for correctness.
	//   If page_insert() fails, remember to free the page you
	//   allocated!

	// LAB 4: Your code here.
	int errortype;
	struct Page * new_page;
	struct Env * check_env;
	
	//check if va is correct
	if (((uint32_t)va >= UTOP) || ((uint32_t)va % PGSIZE != 0)) {
		cprintf("sys_page_alloc: invalid va\n");
		return -E_INVAL;
	}
	
	//check if environmnet is good
	errortype = envid2env(envid, &check_env, 1);
	if (errortype < 0) {
		
		return -E_BAD_ENV;
	}

	//check if permissions are correct
	if (((perm & PTE_U) == 0) || ((perm & PTE_P) == 0)) {
		return -E_INVAL;
	}
	if ((perm & (~PTE_U) & (~PTE_P) & (~PTE_W) & (~PTE_AVAIL)) != 0) {
		return -E_INVAL;
	}
	
	//allocate a page 
	if (page_alloc(&new_page) < 0) {
		return -E_NO_MEM;
	}

	memset(page2kva(new_page), 0, PGSIZE);

	if (page_insert(check_env->env_pgdir, new_page, va, perm) < 0) {
		page_free(new_page);
		return -E_NO_MEM;
	}

	return 0;
}

// Map the page of memory at 'srcva' in srcenvid's address space
// at 'dstva' in dstenvid's address space with permission 'perm'.
// Perm has the same restrictions as in sys_page_alloc, except
// that it also must not grant write access to a read-only
// page.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if srcenvid and/or dstenvid doesn't currently exist,
//		or the caller doesn't have permission to change one of them.
//	-E_INVAL if srcva >= UTOP or srcva is not page-aligned,
//		or dstva >= UTOP or dstva is not page-aligned.
//	-E_INVAL is srcva is not mapped in srcenvid's address space.
//	-E_INVAL if perm is inappropriate (see sys_page_alloc).
//	-E_INVAL if (perm & PTE_W), but srcva is read-only in srcenvid's
//		address space.
//	-E_NO_MEM if there's no memory to allocate the new page,
//		or to allocate any necessary page tables.
static int
sys_page_map(envid_t srcenvid, void *srcva,
	     envid_t dstenvid, void *dstva, int perm)
{
	// Hint: This function is a wrapper around page_lookup() and
	//   page_insert() from kern/pmap.c.
	//   Again, most of the new code you write should be to check the
	//   parameters for correctness.
	//   Use the third argument to page_lookup() to
	//   check the current permissions on the page.

	// LAB 4: Your code here.

	struct Env * srcenv;
	struct Env *dstenv;
	pte_t * page_table_entry_p;
	int errortype;

	//check if permissions are correct
	if (((perm & PTE_U) == 0) || ((perm & PTE_P) == 0)) {
		cprintf("sys page map: perm problem 1, perms are %x \n", perm);
		return -E_INVAL;
	}
	if ((perm & (~PTE_U) & (~PTE_P) & (~PTE_W) & (~PTE_AVAIL)) != 0) {
		cprintf("sys page map: perm 2: too many permissions here is perm %08x \n", perm);
		return -E_INVAL;
	}
	/* how to check if read only if not by range of va? */
	if ((perm && PTE_W != 0) && ((uint32_t)srcva >= UENVS) && ((uint32_t)srcva < UVPT)) {
		cprintf("sys page map: perm 3 \n");
		return -E_INVAL;
	} 
	

	//check the environments
	errortype = envid2env(srcenvid, &srcenv, 1);
	if (errortype < 0) {
		cprintf("sys page map: source is bad env \n");
		return -E_BAD_ENV;
	}
	errortype = envid2env(dstenvid, &dstenv, 0);
	if (errortype < 0) {
		cprintf("sys page map: dest is bad env: ");
		return -E_BAD_ENV;
	}

	//check if va is correct
	if (((uint32_t)dstva >= UTOP) || ((uint32_t)dstva % PGSIZE != 0)) {
		cprintf("sys page map: dstva is incorrect \n");
		return -E_INVAL;
	}
	
	if (((uint32_t)srcva >= UTOP) || ((uint32_t)srcva % PGSIZE != 0)) {
		cprintf("sys page map: srcva is incorrect \n");
		return -E_INVAL;
	}

	page_table_entry_p = pgdir_walk(srcenv->env_pgdir, srcva, 0);
	if (( page_table_entry_p == NULL) || ((*page_table_entry_p & PTE_P) == 0)) 		{
		cprintf("sys_page_map: source va is not mapped \n");
		return -E_INVAL;
	}
	if (page_insert(dstenv->env_pgdir, pa2page(PTE_ADDR(*page_table_entry_p)), dstva, perm) < 0) {
		cprintf("sys page map: not inserted \n");
		return -E_NO_MEM;
	}
	return 0;
	
	
}

// Unmap the page of memory at 'va' in the address space of 'envid'.
// If no page is mapped, the function silently succeeds.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if va >= UTOP, or va is not page-aligned.
static int
sys_page_unmap(envid_t envid, void *va)
{
	// Hint: This function is a wrapper around page_remove().
	struct Env * check_env;
	int errortype;
	
	//check if va is correct
	if (((uint32_t)va >= UTOP) || ((uint32_t)va % PGSIZE != 0)) {
		return -E_INVAL;
	}
	
	//check if environment is good
	errortype = envid2env(envid, &check_env, 1);
	if (errortype < 0) {
		return -E_BAD_ENV;
	}

	page_remove(check_env->env_pgdir, va);
	return 0;
}



// Try to send 'value' to the target env 'envid'.
// If va != 0, then also send page currently mapped at 'va',
// so that receiver gets a duplicate mapping of the same page.
//
// The send fails with a return value of -E_IPC_NOT_RECV if the
// target has not requested IPC with sys_ipc_recv.
//
// Otherwise, the send succeeds, and the target's ipc fields are
// updated as follows:
//    env_ipc_recving is set to 0 to block future sends;
//    env_ipc_from is set to the sending envid;
//    env_ipc_value is set to the 'value' parameter;
//    env_ipc_perm is set to 'perm' if a page was transferred, 0 otherwise.
// The target environment is marked runnable again, returning 0
// from the paused ipc_recv system call.
//
// If the sender sends a page but the receiver isn't asking for one,
// then no page mapping is transferred, but no error occurs.
// The ipc doesn't happen unless no errors occur.
//
// Returns 0 on success where no page mapping occurs,
// 1 on success where a page mapping occurs, and < 0 on error.
// Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist.
//		(No need to check permissions.)
//	-E_IPC_NOT_RECV if envid is not currently blocked in sys_ipc_recv,
//		or another environment managed to send first.
//	-E_INVAL if srcva < UTOP but srcva is not page-aligned.
//	-E_INVAL if srcva < UTOP and perm is inappropriate
//		(see sys_page_alloc).
//	-E_INVAL if srcva < UTOP but srcva is not mapped in the caller's
//		address space.
//	-E_NO_MEM if there's not enough memory to map srcva in envid's
//		address space.
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
	// LAB 4: Your code here
	

	struct Env * dstenv;
	int errortype;

	//check the environment
	errortype = envid2env(envid, &dstenv, 0);
	if (errortype < 0) {
		cprintf("sys ipc try send: dest envid is bad env \n");
		return -E_BAD_ENV;
	}

	if (!dstenv->env_ipc_recving) {
		//cprintf("the environment is not waiting to receive\n");
		return -E_IPC_NOT_RECV;
	}

	if (((uint32_t)srcva < UTOP) && ((uint32_t)srcva % PGSIZE != 0)) {
		cprintf("source is not aligned properly \n");
		return -E_INVAL;
	}

	if (((uint32_t)srcva < UTOP) && ((uint32_t)srcva % PGSIZE != 0)) {
		if (((perm & PTE_U) == 0) || ((perm & PTE_P) == 0)) {
			cprintf("permissions bad\n");
			return -E_INVAL;
		}
		if ((perm & (~PTE_U) & (~PTE_P) & (~PTE_W) & (~PTE_AVAIL)) != 0) {
			cprintf("permissions bad");
			return -E_INVAL;
		}
		
	}


	if ((uint32_t)srcva < UTOP) {

		//check if page table exists for this address
		if ((vpd[PDX(srcva)] & PTE_P) == 0) {
			cprintf("ipc_try_send: srcva is not mapped in source environment\n");
			return -E_INVAL;
		}

		//check if page is mapped
		if ((vpt[((uint32_t)srcva) / PGSIZE] & PTE_P) == 0) {
			cprintf("ipc_try_send: though page table for this page is mapped, the page itself is not \n");
			return -E_INVAL;
		}
	} 

	//check if we try to send mapping but they do not expect it: HOW TO DEAL WITH THIS? I JUST SEND THE VALUE BELOW
//	if (((destEnv->env_ipc_dstva > UTOP) || (destEnv->env_ipc_dstva == 0)) && ((uint32_t)srcva !=0) && ((uint32_t) srcva < UTOP) ) {
//		return 0; //no ipc occurs
//	}

	if (((uint32_t)srcva != 0) && ((uint32_t)srcva > UTOP)) {
		cprintf("srcva is nonzero but above UTOP, I do not know what to do in this case\n");
		panic("do not know what to do\n");
	}


	envid_t this_env = sys_getenvid();
	if (this_env < 0) {
		panic("sys_getenvid returned < 0\n");
	}
	
	if (((uint32_t)srcva != 0) && ((uint32_t)srcva < UTOP) && ((uint32_t) dstenv->env_ipc_dstva != 0) && ((uint32_t) dstenv->env_ipc_dstva < UTOP)) {
		// send page mapping
		if (sys_page_map(this_env, ROUNDDOWN(srcva,PGSIZE), envid, dstenv->env_ipc_dstva, perm) < 0) {
			cprintf("ipc_try_send returns negative value in sys_ipc_try_send\n");
			return -E_NO_MEM;
		}

		dstenv->env_ipc_recving = 0;
		dstenv->env_ipc_from = this_env;
		dstenv->env_ipc_value = value;
		dstenv->env_ipc_perm = perm;
		dstenv->env_status = ENV_RUNNABLE;
		dstenv->env_tf.tf_regs.reg_eax  = 0;

		return 1;
	} else {
		// send just a value
		dstenv->env_ipc_recving = 0;
		dstenv->env_ipc_from = this_env;
		dstenv->env_ipc_value = value;
		dstenv->env_ipc_perm = 0;
		dstenv->env_status = ENV_RUNNABLE;
		dstenv->env_tf.tf_regs.reg_eax  = 0;
		return 0;
	}
}

// Block until a value is ready.  Record that you want to receive
// using the env_ipc_recving and env_ipc_dstva fields of struct Env,
// mark yourself not runnable, and then give up the CPU.
//
// If 'dstva' is < UTOP, then you are willing to receive a page of data.
// 'dstva' is the virtual address at which the sent page should be mapped.
//
// This function only returns on error, but the system call will eventually
// return 0 on success.
// Return < 0 on error.  Errors are:
//	-E_INVAL if dstva < UTOP but dstva is not page-aligned.
static int
sys_ipc_recv(void *dstva)
{
	// LAB 4: Your code here.
	if (((uint32_t)dstva < UTOP) && ((uint32_t)dstva != 0)) {
		if ((uint32_t)dstva % PGSIZE != 0) {
			cprintf("dstva is not properly aligned\n");
			return -E_INVAL;
		}
	}

	curenv->env_ipc_recving = 1;
	curenv->env_ipc_dstva = dstva;
	curenv->env_status = ENV_NOT_RUNNABLE;
	sched_yield();
	panic("error in sys_ipc_recv\n");
}



// Dispatches to the correct kernel function, passing the arguments.
int32_t
syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
{
	// Call the function corresponding to the 'syscallno' parameter.
	// Return any appropriate return value.
	// LAB 3: Your code here.
	switch (syscallno) {
		case SYS_cputs: {
			sys_cputs((char *)a1, a2);
			return 0;	
			}
		case SYS_cgetc: {
			return sys_cgetc();
		}
		case SYS_env_destroy: {
			return sys_env_destroy(a1);
		}
		case SYS_getenvid: {
			return sys_getenvid();
		}
		case SYS_yield: {
			sys_yield();
			return 0; //I do not think it ever returns
		}
		case SYS_exofork: {
			return sys_exofork();			
		}
		case SYS_env_set_status: {
			return sys_env_set_status((envid_t)a1, (int)a2);			
		}
		case SYS_page_alloc: {
			return sys_page_alloc((envid_t)a1, (void *)a2, (int)a3);			
		}
		case SYS_page_map: {
			//cprintf("gets to the sys_page_map \n");
			return sys_page_map((envid_t)a1, (void *)a2, (envid_t)a3, (void *)a4, (int)a5);			
		}
		case SYS_page_unmap: {
			return sys_page_unmap((envid_t)a1, (void *)a2);			
		}
		case SYS_env_set_pgfault_upcall: {
			return sys_env_set_pgfault_upcall((envid_t)a1, (void *)a2);
		}
		case SYS_ipc_recv: {
			return sys_ipc_recv((void *)a1);
		}
		case SYS_ipc_try_send: {
			return sys_ipc_try_send((envid_t)a1, (uint32_t)a2, (void *)a3, (int)a4);
		}
		case SYS_env_set_trapframe: {
			return sys_env_set_trapframe((envid_t)a1, (struct Trapframe *)a2);
		}
		default: {
			return -E_INVAL;
		}
	}
	return -E_INVAL;
	
}

