#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>
#include <kern/fcntl.h>


void sys__exit(int exitcode)
{
  struct addrspace * as;
  struct proc * p = curproc;
  if (exitcode == -2) p->exitcode = _MKWAIT_SIG(129); // not sure what error code is normally, but this is unix std
  else p->exitcode = _MKWAIT_EXIT(exitcode);
  DEBUG(DB_SYSCALL,"Syscall: _exit(%d)\n",exitcode);
  KASSERT(curproc->p_addrspace != NULL);
  
  lock_acquire(proclock);
  int x = listpop(p->kids);
  while (x)
  {
    if(processes->pids[x])
    {
      processes->pids[x]->parent = NULL;
      if (processes->pids[x]->exitcode >= 0)
      {
        lock_release(proclock);
        proc_destroy(processes->pids[x]);
        lock_acquire(proclock);
      }
    }
    x = listpop(p->kids);
  }
  lock_release(proclock);
  listelete(p->kids);
  
  // VFS fields
  if (p->p_cwd)
  {
    VOP_DECREF(p->p_cwd);
    p->p_cwd = NULL;
  }
  
  as_deactivate();
  as = curproc_setas(NULL);
  as_destroy(as);

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

  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) || p->parent->exitcode >= 0 || p->parent == kproc) proc_destroy(p);
  
  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);
}

int sys_waitpid(pid_t pid, userptr_t status, int options, pid_t * retval)
{
  if (options != 0 || !(void *)(status))
  {
    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))
  {
    lock_acquire(target->waitlock);
    cv_wait(target->waiting, target->waitlock);
    lock_release(target->waitlock);
  }
  int exitstatus = target->exitcode;
  *retval = target->exitcode;
  int result = copyout((void *)&exitstatus, status, sizeof(int));
  if (result)
  {
    return result;
  }
  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");
  if ((!child)) return ENOMEM;
  
  struct trapframe * new_tf = kmalloc(sizeof(*tf));
  struct addrspace * new_as;
  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;
}

// this function takes an argc, argv, and puts argv into the userptr argvaddr, on the stack stackptr
int copyArgs(int argc, char ** argv, userptr_t * argvAddr, vaddr_t * stackptr)
{
  vaddr_t stack = *stackptr; // for ease of referencing
  char ** newArgv = kmalloc(sizeof(char *) * (argc + 1)); // need argc + 1 space since we want a final address to be null
  size_t wasteOfSpace;
  int errcode;
  
  for(int i = 0; i < argc; ++i)
  {
    int arglen = strlen(*(argv + i)) + 1; // length of char array for this arg
    stack -= ROUNDUP(arglen, 8); // make the stack bigger by this length, but rounded to 8 cause bytes
  errcode = copyoutstr(*(argv + i), (userptr_t)stack, arglen, &wasteOfSpace); // take the string in argv at i, put it at the stack position we calculated just before as the start for a string of our length
    if(errcode)
    {
      kfree(newArgv);
      return errcode; // if an error, fuck life
    }
    *(newArgv + i) = (char *)stack; // our argv kernel array is going to contain the user space address
  }
  
  *(newArgv + argc) = NULL; // set final address to NULL
  
  for(int i = 0; i <= argc; ++i) // after we store the array contents, we want to store the array of pointers to those contents. which is "before" in the stack becuase fuck you, that's why
  {
    stack -= sizeof(char *); // move the stack pointer back one pointer worth of space
    errcode = copyout(newArgv + (argc - i), (userptr_t)stack, sizeof(char *)); // copy the pointers in reverse order into the stack, since we are going backwards in the stack.
    if(errcode)
    {
      kfree(newArgv);
      return errcode; // cry. just cry. then return your error.
    }
  }

  *argvAddr = (userptr_t)stack; // set the argv array in userland to start at where we put it (current stackptr location)
  if (stack % 8 == 0) stack -= 8; // move the stack pointer back the minimum amount (if we are at an 8 byte address already, a full 8 bytes. otherwise we must be at a 4 byte address, so just half that)
  else stack -= 4;
  *stackptr = stack; // set the real stack pointer to the one we've been fucking with
  kfree(newArgv); // all data copied over, can free our temp array now
  return 0; // return no error
}

static void freeArgv(char ** argv, int len)
{
  for (int i = 0; i <= len; ++i)
  {
    kfree(argv[i]);
  }
}

// args is an array of null terminated strings, with the last element being a null pointer so we don't overflow if iterating
// since this is a userptr type, we need to use copyin and copyout to get data properly
// hopefully the path in program is actually a full filepath
int sys_execv(const char * program, userptr_t args)
{
  userptr_t temp = args;
  int argcount = 0;
  int errcode; // why don't we have errno!!! my beautful one-liners!!!
  
  // see how long args is
  while (1)
  {
    void * temp2 = kmalloc(sizeof(temp));
    errcode = copyin(temp, temp2, sizeof(temp2));
    if (errcode) return errcode;
    if (!(*(char **)temp2)) break;
    temp += sizeof(temp);
    kfree(temp2);
    ++argcount;
  }

  // allocate space for argv
  char ** argv = kmalloc(argcount * sizeof(char *));
  if (!argv) return ENOMEM;
  
  // get kernel copy of args in argv
  temp = args;
  for (int i = 0; i < argcount; ++i)
  {
    char * useraddr = kmalloc(sizeof(temp));
    if (!(useraddr))
    {
      kfree(argv);
      return ENOMEM;
    }
    // useraddr is now the address of the string in userland
    errcode = copyin(temp, useraddr, sizeof(temp));
    if (errcode)
    {
      kfree(argv);
      return errcode;
    }
    // change argv[i] to be a string of length enough
    int strLen = strlen(*(char **)useraddr) + 1;
    kfree(useraddr);
    size_t wasteOfSpace;
    argv[i] = kmalloc(strLen * sizeof(char));
    if (!(argv[i]))
    {
      freeArgv(argv, i);
      kfree(argv);
      return ENOMEM;
    }
    
    errcode = copyinstr((userptr_t)*(char **)temp, argv[i], strLen, &wasteOfSpace);
    if (errcode)
    {
      freeArgv(argv, i);
      kfree(argv);
      return errcode;
    }
    temp += sizeof(temp);
  }

  // allocate space for filepath
  char * filepath = kmalloc((strlen(program) + 1) * sizeof(char));
  if (!(filepath))
  {
    freeArgv(argv, argcount - 1);
    kfree(argv);
    return ENOMEM;
  }
 
  // get kernel copy of filepath
  size_t useless;
  errcode = copyinstr((const_userptr_t)program, filepath, strlen(program) + 1, &useless);
  if (errcode)
  {
    freeArgv(argv, argcount - 1);
    kfree(argv);
    kfree(filepath);
    return errcode;
  }
 
  // open program file
  struct vnode * v;
  errcode = vfs_open(filepath, O_RDONLY, 0, &v);
  if (errcode)
  {
    freeArgv(argv, argcount - 1);
    kfree(argv);
    kfree(filepath);
    return errcode;
  }
  
  // create new address space
  struct addrspace * as = as_create();
  if (!(as))
  {
    vfs_close(v);
    freeArgv(argv, argcount - 1);
    kfree(argv);
    kfree(filepath);
    return ENOMEM;
  }

  // Switch to the new as and activate. keep old in case we fail later
  struct addrspace * oldas = curproc_getas();
  curproc_setas(as);
  as_activate();

  vaddr_t entrypoint;
  // Load santa's favourite slave
  errcode = load_elf(v, &entrypoint);
  if (errcode)
  {
    vfs_close(v);
    freeArgv(argv, argcount - 1);
    kfree(argv);
    kfree(filepath);
    curproc_setas(oldas);
    as_destroy(as);
    return errcode;
  }

  // close the file now that we're done
  vfs_close(v);
  
  // create the new stack
  vaddr_t newstack;
  errcode = as_define_stack(as, &newstack);
  if (errcode)
  {
    freeArgv(argv, argcount - 1);
    kfree(argv);
    kfree(filepath);
    curproc_setas(oldas);
    as_destroy(as);
    return errcode;
  }

  // put args into the stack
  userptr_t userV;
  errcode = copyArgs(argcount, argv, &userV, &newstack);
  if (errcode)
  {
    freeArgv(argv, argcount - 1);
    kfree(argv);
    kfree(filepath);
    curproc_setas(oldas);
    as_destroy(as);
    return errcode;
  }
  
  // delete old addrspace, enter new process
  as_destroy(oldas);
  kfree(filepath);
  freeArgv(argv, argcount - 1);
  kfree(argv);
  enter_new_process(argcount, userV, newstack, entrypoint);
  panic("Enter new process returned!\n");
  return EINVAL;
}

