#include <types.h>
#include <kern/errno.h>
#include <kern/unistd.h>
#include <kern/wait.h>
#include <mips/trapframe.h>
#include <lib.h>
#include <vfs.h>
#include <vnode.h>
#include <syscall.h>
#include <current.h>
#include <proc.h>
#include <thread.h>
#include <addrspace.h>
#include <copyinout.h>
#include <synch.h>

  /* this implementation of sys__exit does not do anything with the exit code */
  /* this needs to be fixed to get exit() and waitpid() working properly */

/*
logic for waiting:

on exit: broadcast. then
if you can (parent is exited, check exitcode), DELETE
if not, this means you are waiting for a parent to die or call wait on you
  so, if they call wait, they can call the death for you
  if they just kill themselves, they can do that then kill you too

on call of waitpid:
wait on CV of child, it will broadcast when exiting
when you wake up within waitpid, kid has exited. delete the kid.
continue on your merry day
*/

/*
void waitpid()
{
  cv_wait(kid); // if valid thing to wait on, ofc
  fullDelete(kid);
}*/

/*
void fullDelete() // this should just be delete_proc or whatever
{
  
}*/


/*
void(exit)
{
  // at this point, all unneeded shit is gone
  cv_broadcast(this->waiting);
  if (parent->exitcode >= 0) fullDelete(this);
  else return; // parent will clean you up
}*/

////////////////////////////////////////////////////////////////////////////

/*void sys__exit(int exitcode)
{
  //struct addrspace * as;
  struct proc * p = curproc;
  p->exitcode = exitcode;
  DEBUG(DB_SYSCALL,"Syscall: _exit(%d)\n",exitcode);
  //KASSERT(curproc->p_addrspace != NULL);
  //as_deactivate();
  //as = curproc_setas(NULL);
  //as_destroy(as);

  //kprintf("DESTROYED AS\n");
  // detach this thread from its process
  // note: curproc cannot be used after this call
  proc_remthread(curthread);
  //listelete(p->kids);
  kprintf("DESTROYED KIDS\n");
  //threadarray_cleanup(&p->p_threads);
	//spinlock_cleanup(&p->p_lock);
	lock_acquire(p->waitlock);
  cv_broadcast(p->waiting, p->waitlock);
  lock_release(p->waitlock);
  kprintf("BROADCASTED\n");
  
  if (!(p->parent)) proc_destroy(p);
  if (p->parent->exitcode >= 0) proc_destroy(p);
  kprintf("EXITED THREAD\n");
  thread_exit();
  // thread_exit() does not return, so we should never get here
  panic("return from thread_exit in sys_exit\n");
}*/

void sys__exit(int exitcode)
{
  struct addrspace * as;
  struct proc * p = curproc;
  p->exitcode = exitcode;
  DEBUG(DB_SYSCALL,"Syscall: _exit(%d)\n",exitcode);
  KASSERT(curproc->p_addrspace != NULL);
	/* VFS fields */
	if (p->p_cwd)
	{
		VOP_DECREF(p->p_cwd);
		p->p_cwd = NULL;
	}

  as_deactivate();
  as = curproc_setas(NULL);
  as_destroy(as);
  
  if (p->console)
	{
	  vfs_close(p->console);
	}

  /* detach this thread from its process */
  /* note: curproc cannot be used after this call */
  proc_remthread(curthread);

  int x = listpop(p->kids);
  while (x)
  {
    processes->pids[x]->parent = NULL;
    x = listpop(p->kids);
  }
  
  listelete(p->kids);
  threadarray_cleanup(&p->p_threads);
	spinlock_cleanup(&p->p_lock);
	
	lock_acquire(p->waitlock);
  cv_broadcast(p->waiting, p->waitlock);
  lock_release(p->waitlock);
  
  if (!(p->parent)) proc_destroy(p);
  else if (p->parent->exitcode >= 0)
  {
    proc_destroy(p);
  }
  
  /* if this is the last user process in the system, proc_destroy()
     will wake up the kernel menu thread */
  //proc_destroy(p);
  if (!(curproc)) thread_exit();
  /* thread_exit() does not return, so we should never get here */
  panic("return from thread_exit in sys_exit\n");
}


// basically, return an error code, and put the actual result in retval
int sys_getpid(pid_t * retval)
{
  if (!(curproc)) return 1;
  *retval = curproc->pid;
  return(0);
}

/*
void waitpid()
{
  cv_wait(kid); // if valid thing to wait on, ofc
  fullDelete(kid);
}*/

/* stub handler for waitpid() system call                */
int sys_waitpid(pid_t pid, userptr_t status, int options, pid_t * retval)
{
  kprintf("Waiting on: %d\n", pid);
  (void)status;
  if (options != 0)
  {
    return(EINVAL);
  }
  
  if (pid < PID_MIN || pid > PID_MAX) return ESRCH;
  struct proc * target = getChild(curproc, pid);
  
  if (!(target)) return ECHILD;
  if (target->exitcode >= 0)
  {
    *retval = target->exitcode;
    return 0;
  }
  
  lock_acquire(target->waitlock);
  cv_wait(target->waiting, target->waitlock);
  lock_release(target->waitlock);
  *retval = target->exitcode;
  proc_destroy(target);
  return 0;
}

int sys_fork(struct trapframe * tf, int * retval)
{
  // create new process' memory space
  struct proc * child = proc_create_runprogram("childproc");
  struct addrspace * new_as;
  struct trapframe * new_tf = kmalloc(sizeof(*tf));
  
  if ((!child)) return ENOMEM;
  as_copy(curproc_getas(), &new_as);
  
  if (!(new_as) || !(new_tf))
  {
    proc_destroy(child);
    kfree(new_as);
    kfree(new_tf);
    return ENOMEM;
  }
  
  // set PIDs, etc. copy data in to the new space
  child->p_addrspace = new_as;
  child->parent = curproc;
  add_child(curproc, child->pid);
  *new_tf = *tf;
  
    // start new thread
  thread_fork("childproc", child, enter_forked_process, new_tf, 0);
  
  // return correct values
  if (curproc->pid == child->pid) *retval = 0;
  else *retval = child->pid;
  return 0;
}
