/*
Copyright (C) 2015-2019 The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file LICENSE for details.
*/

#include "process.h"
#include "kobject.h"
#include "page.h"
#include "string.h"
#include "list.h"
#include "x86.h"
#include "interrupt.h"
#include "memorylayout.h"
#include "kmalloc.h"
#include "kernel/types.h"
#include "kernelcore.h"
#include "main.h"
#include "keyboard.h"
#include "clock.h"

struct process *current = 0;
struct list ready_list = { 0, 0 };
struct list grave_list = { 0, 0 };
struct list grave_watcher_list = { 0, 0 };	// parent processes are put here to wait for their children
struct process *process_table[PROCESS_MAX_PID] = { 0 };

void process_init()
{
	current = process_create();

	pagetable_load(current->pagetable);
	pagetable_enable();

	current->state = PROCESS_STATE_READY;

	current->waiting_for_child_pid = 0;
}

void process_kstack_reset(struct process *p, unsigned entry_point)
{
	struct x86_stack *s;

	p->state = PROCESS_STATE_CRADLE;

	s = (struct x86_stack *) p->kstack_ptr;

	s->regs2.ebp = (uint32_t) (p->kstack_ptr + 28);
	s->old_ebp = (uint32_t) (p->kstack_ptr + 32);
	s->old_eip = (unsigned) intr_return;
	s->fs = 0;
	s->gs = 0;
	s->es = X86_SEGMENT_USER_DATA;
	s->ds = X86_SEGMENT_USER_DATA;
	s->cs = X86_SEGMENT_USER_CODE;
	s->eip = entry_point;
	s->eflags.interrupt = 1;
	s->eflags.iopl = 3;
	s->esp = PROCESS_STACK_INIT;
	s->ss = X86_SEGMENT_USER_DATA;
}

void process_kstack_copy(struct process *parent, struct process *child)
{
	child->kstack_top = child->kstack + PAGE_SIZE - 8;
	child->kstack_ptr = child->kstack_top - sizeof(struct x86_stack);

	struct x86_stack *child_regs = (struct x86_stack *) child->kstack_ptr;
	struct x86_stack *parent_regs = (struct x86_stack *) (parent->kstack_top - sizeof(struct x86_stack));

	*child_regs = *parent_regs;

	child_regs->regs2.ebp = (uint32_t) (child->kstack_ptr + 28);
	child_regs->old_ebp = (uint32_t) (child->kstack_ptr + 32);
	child_regs->old_eip = (unsigned) intr_return;
	child_regs->regs1.eax = 0;
}

/*
Valid pids start at 1 and go to PROCESS_MAX_PID.
To avoid confusion, keep picking increasing
pids until it is necessary to wrap around.
"last" is the most recently selected pid.
*/

static int process_allocate_pid()
{
	static int last = 0;

	int i;

	for(i = last + 1; i < PROCESS_MAX_PID; i++) {
		if(!process_table[i]) {
			last = i;
			return i;
		}
	}

	for(i = 1; i < last; i++) {
		if(!process_table[i]) {
			last = i;
			return i;
		}
	}

	return 0;
}

void process_selective_inherit(struct process *parent, struct process *child, int * fds, int length)
{
	int i;

	for (i=0;i<length;i++) {
		if(fds[i]>-1) {
			child->ktable[i] = kobject_copy(parent->ktable[fds[i]]);
		} else {
			child->ktable[i] = 0;
		}
	}

	child->ppid = parent->pid;
}

void process_inherit(struct process *parent, struct process *child)
{
	/* Child inherits everything parent inherits */
	int i;
	int * fds = kmalloc(sizeof(int)*PROCESS_MAX_OBJECTS);
	for (i = 0; i < PROCESS_MAX_OBJECTS; i++)
	{
		if (parent->ktable[i]) {
			fds[i] = i;
		} else {
			fds[i] = -1;
		}
	}
	process_selective_inherit(parent, child, fds, PROCESS_MAX_OBJECTS);
	kfree(fds);
}

int process_data_size_set(struct process *p, unsigned size)
{
	// XXX check valid ranges
	// XXX round up to page size

	if(size % PAGE_SIZE) {
		size += (PAGE_SIZE - size % PAGE_SIZE);
	}

	if(size > p->vm_data_size) {
		uint32_t start = PROCESS_ENTRY_POINT + p->vm_data_size;
		pagetable_alloc(p->pagetable, start, size, PAGE_FLAG_USER | PAGE_FLAG_READWRITE | PAGE_FLAG_CLEAR);
	} else if(size < p->vm_data_size) {
		uint32_t start = PROCESS_ENTRY_POINT + size;
		pagetable_free(p->pagetable, start, p->vm_data_size);
	} else {
		// requested size is equal to current.
	}

	p->vm_data_size = size;
	pagetable_refresh();

	return 0;
}

int process_stack_size_set(struct process *p, unsigned size)
{
	// XXX check valid ranges
	// XXX round up to page size

	if(size > p->vm_stack_size) {
		uint32_t start = -size;
		pagetable_alloc(p->pagetable, start, size - p->vm_stack_size, PAGE_FLAG_USER | PAGE_FLAG_READWRITE | PAGE_FLAG_CLEAR);
	} else {
		uint32_t start = -p->vm_stack_size;
		pagetable_free(p->pagetable, start, p->vm_stack_size - size);
	}

	p->vm_stack_size = size;
	pagetable_refresh();

	return 0;
}

void process_stack_reset(struct process *p, unsigned size)
{
	process_stack_size_set(p, size);
	memset((void *) -size, size, 0);
}

struct process *process_create()
{
	struct process *p;

	p = page_alloc(1);

	p->pid = process_allocate_pid();
	process_table[p->pid] = p;

	p->pagetable = pagetable_create();
	pagetable_init(p->pagetable);

	p->vm_data_size = 0;
	p->vm_stack_size = 0;

	process_data_size_set(p, 2 * PAGE_SIZE);
	process_stack_size_set(p, 2 * PAGE_SIZE);

	p->kstack = page_alloc(1);
	p->kstack_top = p->kstack + PAGE_SIZE - 8;
	p->kstack_ptr = p->kstack_top - sizeof(struct x86_stack);

	process_kstack_reset(p, PROCESS_ENTRY_POINT);

	// XXX table should be allocated
	int i;
	for(i = 0; i < PROCESS_MAX_OBJECTS; i++) {
		p->ktable[i] = 0;
	}

	p->state = PROCESS_STATE_READY;

	return p;
}

void process_delete(struct process *p)
{
	int i;
	for(i = 0; i < PROCESS_MAX_OBJECTS; i++) {
		if(p->ktable[i]) {
			kobject_close(p->ktable[i]);
		}
	}
	pagetable_delete(p->pagetable);
	page_free(p->kstack);
	page_free(p);
	process_table[p->pid] = 0;
}

void process_launch(struct process *p)
{
	list_push_tail(&ready_list, &p->node);
}

static void process_switch(int newstate)
{
	interrupt_block();

	if(current) {
		if(current->state != PROCESS_STATE_CRADLE) {
			asm("pushl %ebp");
			asm("pushl %edi");
			asm("pushl %esi");
			asm("pushl %edx");
			asm("pushl %ecx");
			asm("pushl %ebx");
			asm("pushl %eax");
		      asm("movl %%esp, %0":"=r"(current->kstack_ptr));
		}

		interrupt_stack_pointer = (void *) INTERRUPT_STACK_TOP;
		current->state = newstate;

		if(newstate == PROCESS_STATE_READY) {
			list_push_tail(&ready_list, &current->node);
		}
		if(newstate == PROCESS_STATE_GRAVE) {
			list_push_tail(&grave_list, &current->node);
		}
	}

	current = 0;

	while(1) {
		current = (struct process *) list_pop_head(&ready_list);
		if(current)
			break;

		interrupt_unblock();
		interrupt_wait();
		interrupt_block();
	}

	current->state = PROCESS_STATE_RUNNING;
	interrupt_stack_pointer = current->kstack_top;

	asm("movl %0, %%cr3"::"r"(current->pagetable));
	asm("movl %0, %%esp"::"r"(current->kstack_ptr));

	asm("popl %eax");
	asm("popl %ebx");
	asm("popl %ecx");
	asm("popl %edx");
	asm("popl %esi");
	asm("popl %edi");
	asm("popl %ebp");

	interrupt_unblock();
}

int allow_preempt = 0;

void process_preempt()
{
	if(allow_preempt && current && ready_list.head) {
		process_switch(PROCESS_STATE_READY);
	}
}

void process_yield()
{
	/* no-op if process module not yet initialized. */
	if(!current) return;
	process_switch(PROCESS_STATE_READY);
}

void process_exit(int code)
{
	// printf("process %d exiting with status %d...\n", current->pid, code); --> transport to kshell run
	current->exitcode = code;
	current->exitreason = PROCESS_EXIT_NORMAL;
	process_wakeup_parent(&grave_watcher_list);	// On exit, wake up parent if need be
	process_switch(PROCESS_STATE_GRAVE);
}

void process_wait(struct list *q)
{
	list_push_tail(q, &current->node);
	process_switch(PROCESS_STATE_BLOCKED);
}

void process_wakeup(struct list *q)
{
	struct process *p;
	p = (struct process *) list_pop_head(q);
	if(p) {
		p->state = PROCESS_STATE_READY;
		list_push_tail(&ready_list, &p->node);
	}
}

void process_reap_all()
{
	struct process *p;
	while((p = (struct process *) list_pop_head(&grave_list))) {
		process_delete(p);
	}
}

/* Wakes up parent off of the corresponding list*/
void process_wakeup_parent(struct list *q)
{
	struct process *p = (struct process *) q->head;
	// Loop through all the waiting parents to see if one needs to be woken up
	while(p) {
		if(p->pid == current->ppid && (p->waiting_for_child_pid == 0 || p->waiting_for_child_pid == current->pid)) {
			p->state = PROCESS_STATE_READY;
			p->waiting_for_child_pid = 0;
			list_remove(&p->node);
			list_push_tail(&ready_list, &p->node);
			break;
		}
		p = (struct process *) (&p->node)->next;
	}
}

void process_wakeup_all(struct list *q)
{
	struct process *p;
	while((p = (struct process *) list_pop_head(q))) {
		p->state = PROCESS_STATE_READY;
		list_push_tail(&ready_list, &p->node);
	}
}

void process_dump(struct process *p)
{
	struct x86_stack *s = (struct x86_stack *) (INTERRUPT_STACK_TOP - sizeof(*s));
	printf("kstack: %x\n", p->kstack);
	printf("stackp: %x\n", p->kstack_ptr);
	printf("eax: %x     cs: %x\n", s->regs1.eax, s->cs);
	printf("ebx: %x     ds: %x\n", s->regs1.ebx, s->ds);
	printf("ecx: %x     ss: %x\n", s->regs1.ecx, s->ss);
	printf("edx: %x eflags: %x\n", s->regs1.edx, s->eflags);
	printf("esi: %x\n", s->regs1.esi);
	printf("edi: %x\n", s->regs1.edi);
	printf("ebp: %x\n", s->regs1.ebp);
	printf("esp: %x\n", s->esp);
	printf("eip: %x\n", s->eip);
}

int process_available_fd(struct process *p)
{
	struct kobject **fdtable = current->ktable;
	int i;
	for(i = 0; i < PROCESS_MAX_OBJECTS; i++) {
		if(fdtable[i] == 0)
			return i;
	}
	return -1;
}

int process_object_max(struct process *p)
{
	struct kobject **fdtable = current->ktable;
	int i;
	// Because of 0-indexing, PROCESS_MAX_OBJECTS is the size and
	// therefor 1 offset, don't look for 0 there.
	for(i = PROCESS_MAX_OBJECTS - 1; i > -1; i--) {
		if(fdtable[i] != 0)
			return i;
	}
	return -1;
}

void process_make_dead(struct process *dead)
{
	int i;
	for(i = 0; i < PROCESS_MAX_PID; i++) {
		if(process_table[i] && process_table[i]->ppid == dead->pid) {
			process_make_dead(process_table[i]);
		}
	}
	dead->exitcode = 0;
	dead->exitreason = PROCESS_EXIT_KILLED;
	if(dead == current) {
		process_switch(PROCESS_STATE_GRAVE);
	} else {
		list_remove(&dead->node);
		list_push_tail(&grave_list, &dead->node);
	}
}

int process_kill(uint32_t pid)
{
	if(pid > 0 && pid <= PROCESS_MAX_PID) {
		struct process *dead = process_table[pid];
		if(dead) {
			printf("process killed\n");
			process_make_dead(dead);
			return 0;
		} else {
			return 1;
		}
	} else {
		return 1;
	}
}

int process_wait_child(uint32_t pid, struct process_info *info, int timeout)
{
	clock_t start, elapsed;
	uint32_t total;

	if(!info)
		return -1;

	start = clock_read();

	do {
		struct process *p = (struct process *) (grave_list.head);
		while(p) {
			struct process *next = (struct process *) p->node.next;
			if((pid != 0 && p->pid == pid) || (p->ppid == current->pid)) {
				info->exitcode = p->exitcode;
				info->exitreason = p->exitreason;
				info->pid = p->pid;
				return p->pid;
			}
			p = next;
		}

		current->waiting_for_child_pid = pid;
		process_wait(&grave_watcher_list);

		elapsed = clock_diff(start, clock_read());
		total = elapsed.millis + elapsed.seconds * 1000;
	} while(total < timeout || timeout < 0);

	return 0;
}

int process_reap(uint32_t pid)
{
	struct process *p = (struct process *) (grave_list.head);
	while(p) {
		struct process *next = (struct process *) p->node.next;
		if(p->pid == pid) {
			list_remove(&p->node);
			process_delete(p);
			return 0;
		}
		p = next;
	}
	return 1;
}

/*
process_pass_arguments set up the current process stack
so that it contains a copy of p->args and p->args_length
at the very top, serving as the initial arguments to the entry function:
void entry( const char *args, int args_length );
*/

#define PUSH_INTEGER( value ) esp -= sizeof(int); *((int*)esp)=(int)(value);

void process_pass_arguments(struct process *p, int argc, char **argv)
{
	/* Get the default stack pointer position. */
	char *esp = (char *) PROCESS_STACK_INIT;

	/* Make a local array to keep track of user addresses. */
	char **addr_of_argv = kmalloc(sizeof(char *) * argc);

	/* For each argument, in reverse order: */
	int i;
	for(i = (argc - 1); i >= 0; i--) {
		/* Size is strlen plus null terminator, integer aligned. */
		int length = strlen(argv[i]) + 1;
		if(length % 4) {
			length += (4 - length % 4);
		}
		esp -= length;

		/* Push length bytes onto the stack. */
		memcpy(esp, argv[i], length);

		/* Remember that address for later */
		addr_of_argv[i] = esp;
	}

	/* Push each item of argc, in reverse order. */
	for(i = (argc - 1); i >= 0; i--) {
		PUSH_INTEGER(addr_of_argv[i]);
	}

	/* Keep the address of the array start. */
	const char *addr_of_addr_of_argv = esp;

	/* Push the arguments to main */
	PUSH_INTEGER(addr_of_addr_of_argv);
	PUSH_INTEGER(argc);

	/* Final stack pointer should be below the last item. */
	esp -= 4;

	/* Set the starting stack pointer on the kstack to this new value */
	struct x86_stack *s = (struct x86_stack *) p->kstack_ptr;
	s->esp = (int) (esp);

	kfree(addr_of_argv);
}

int process_stats(int pid, struct process_stats *s)
{
	if(pid > PROCESS_MAX_PID || !process_table[pid]) {
		return 1;
	}
	*s = process_table[pid]->stats;
	return 0;
}
