#include <inc/mmu.h>
#include <inc/x86.h>
#include <inc/assert.h>

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

static struct Taskstate ts;

/* Interrupt descriptor table.  (Must be built at run time because
 * shifted function addresses can't be represented in relocation records.)
 */
struct Gatedesc idt[256] = { { 0 } };
struct Pseudodesc idt_pd = {
	sizeof(idt) - 1, (uint32_t) idt
};


static const char *trapname(int trapno)
{
	static const char * const excnames[] = {
		"Divide error",
		"Debug",
		"Non-Maskable Interrupt",
		"Breakpoint",
		"Overflow",
		"BOUND Range Exceeded",
		"Invalid Opcode",
		"Device Not Available",
		"Double Falt",
		"Coprocessor Segment Overrun",
		"Invalid TSS",
		"Segment Not Present",
		"Stack Fault",
		"General Protection",
		"Page Fault",
		"(unknown trap)",
		"x87 FPU Floating-Point Error",
		"Alignment Check",
		"Machine-Check",
		"SIMD Floating-Point Exception"
	};

	if (trapno < sizeof(excnames)/sizeof(excnames[0]))
		return excnames[trapno];
	if (trapno == T_SYSCALL)
		return "System call";
	if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16)
		return "Hardware Interrupt";
	return "(unknown trap)";
}
extern int * h0();
extern int * h1();
extern int * h2();
extern int * h3();
extern int * h4();
extern int * h5();
extern int * h6();
extern int * h7();
extern int * h8();
extern int * h9();
extern int * h10();
extern int * h11();
extern int * h12();
extern int * h13();
extern int * h14();
extern int * h15();
extern int * h16();
extern int * h17();
extern int * h18();
extern int * h19();
extern int * h20();
extern int * h21();
extern int * h22();
extern int * h23();
extern int * h24();
extern int * h25();
extern int * h26();
extern int * h27();
extern int * h28();
extern int * h29();
extern int * h30();
extern int * h31();
extern int * h32();
extern int * h33();
extern int * h34();
extern int * h35();
extern int * h36();
extern int * h37();
extern int * h38();
extern int * h39();
extern int * h40();
extern int * h41();
extern int * h42();
extern int * h43();
extern int * h44();
extern int * h45();
extern int * h46();
extern int * h47();
extern int * h48();


void
idt_init(void)
{
	cprintf("entering idt init \n");
	extern struct Segdesc gdt[];
	
	// LAB 3: Your code here.
	int i;
	//[0, 31 i:1 p:0]
	SETGATE(idt[0], 1, GD_KT, h0, 0);
	SETGATE(idt[1], 1, GD_KT, h1, 0);
	SETGATE(idt[2], 1, GD_KT, h2, 0);
	SETGATE(idt[3], 1, GD_KT, h3, 3);
	SETGATE(idt[4], 1, GD_KT, h4, 0);
	SETGATE(idt[5], 1, GD_KT, h5, 0);
	SETGATE(idt[6], 1, GD_KT, h6, 0);
	SETGATE(idt[7], 1, GD_KT, h7, 0);
	SETGATE(idt[8], 1, GD_KT, h8, 0);
	SETGATE(idt[9], 1, GD_KT, h9, 0);
	SETGATE(idt[10], 1, GD_KT, h10, 0);
	SETGATE(idt[11], 1, GD_KT, h11, 0);
	SETGATE(idt[12], 1, GD_KT, h12, 0);
	SETGATE(idt[13], 1, GD_KT, h13, 0);
	SETGATE(idt[14], 1, GD_KT, h14, 0);
	SETGATE(idt[15], 1, GD_KT, h15, 0);
	SETGATE(idt[16], 1, GD_KT, h16, 0);
	SETGATE(idt[17], 1, GD_KT, h17, 0);
	SETGATE(idt[18], 1, GD_KT, h18, 0);
	SETGATE(idt[19], 1, GD_KT, h19, 0);
	SETGATE(idt[20], 1, GD_KT, h20, 0);
	SETGATE(idt[21], 1, GD_KT, h21, 0);
	SETGATE(idt[22], 1, GD_KT, h22, 0);
	SETGATE(idt[23], 1, GD_KT, h23, 0);
	SETGATE(idt[24], 1, GD_KT, h24, 0);
	SETGATE(idt[25], 1, GD_KT, h25, 0);
	SETGATE(idt[26], 1, GD_KT, h26, 0);
	SETGATE(idt[27], 1, GD_KT, h27, 0);
	SETGATE(idt[28], 1, GD_KT, h28, 0);
	SETGATE(idt[29], 1, GD_KT, h29, 0);
	SETGATE(idt[30], 1, GD_KT, h30, 3);
	SETGATE(idt[31], 1, GD_KT, h31, 0);
	
	//32, 48, i:0, p:3

	SETGATE(idt[32], 0, GD_KT, h32, 3);
	SETGATE(idt[33], 0, GD_KT, h33, 3);
	SETGATE(idt[34], 0, GD_KT, h34, 3);
	SETGATE(idt[35], 0, GD_KT, h35, 3);
	SETGATE(idt[36], 0, GD_KT, h36, 3);
	SETGATE(idt[37], 0, GD_KT, h37, 3);
	SETGATE(idt[38], 0, GD_KT, h38, 3);
	SETGATE(idt[39], 0, GD_KT, h39, 3);
	SETGATE(idt[40], 0, GD_KT, h40, 3);
	SETGATE(idt[41], 0, GD_KT, h41, 3);
	SETGATE(idt[42], 0, GD_KT, h42, 3);
	SETGATE(idt[43], 0, GD_KT, h43, 3);
	SETGATE(idt[44], 0, GD_KT, h44, 3);
	SETGATE(idt[45], 0, GD_KT, h45, 3);
	SETGATE(idt[46], 0, GD_KT, h46, 3);
	SETGATE(idt[47], 0, GD_KT, h47, 3);
	SETGATE(idt[48], 0, GD_KT, h48, 3);
	


	// Setup a TSS so that we get the right stack
	// when we trap to the kernel.
	ts.ts_esp0 = KSTACKTOP;
	ts.ts_ss0 = GD_KD;

	// Initialize the TSS field of the gdt.
	gdt[GD_TSS >> 3] = SEG16(STS_T32A, (uint32_t) (&ts),
					sizeof(struct Taskstate), 0);
	gdt[GD_TSS >> 3].sd_s = 0;

	// Load the TSS
	ltr(GD_TSS);

	// Load the IDT
	asm volatile("lidt idt_pd");
}

void
print_trapframe(struct Trapframe *tf)
{
	cprintf("TRAP frame at %p\n", tf);
	print_regs(&tf->tf_regs);
	cprintf("  es   0x----%04x\n", tf->tf_es);
	cprintf("  ds   0x----%04x\n", tf->tf_ds);
	cprintf("  trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno));
	cprintf("  err  0x%08x\n", tf->tf_err);
	cprintf("  eip  0x%08x\n", tf->tf_eip);
	cprintf("  cs   0x----%04x\n", tf->tf_cs);
	cprintf("  flag 0x%08x\n", tf->tf_eflags);
	cprintf("  esp  0x%08x\n", tf->tf_esp);
	cprintf("  ss   0x----%04x\n", tf->tf_ss);
}

void
print_regs(struct PushRegs *regs)
{
	cprintf("  edi  0x%08x\n", regs->reg_edi);
	cprintf("  esi  0x%08x\n", regs->reg_esi);
	cprintf("  ebp  0x%08x\n", regs->reg_ebp);
	cprintf("  oesp 0x%08x\n", regs->reg_oesp);
	cprintf("  ebx  0x%08x\n", regs->reg_ebx);
	cprintf("  edx  0x%08x\n", regs->reg_edx);
	cprintf("  ecx  0x%08x\n", regs->reg_ecx);
	cprintf("  eax  0x%08x\n", regs->reg_eax);
}

static void
trap_dispatch(struct Trapframe *tf)
{
	// Handle processor exceptions.
	// LAB 3: Your code here.

	// Handle clock and serial interrupts.
	// LAB 4: Your code here.
	uint32_t ret;

	switch (tf->tf_trapno) {
	case 0: { break;}
	case T_PGFLT: {//page fault 
			
			cprintf("dispatched to page fault \n");			
			
			page_fault_handler(tf);

			break; 
		}
	case T_BRKPT:
	case T_DEBUG: {
			//breakpoint
			cprintf("dispatched to breakpoint \n");	
			if (better_monitor(tf) == 0) {
				return; //the environment will keep running
			}
			break;
		}
	case T_SYSCALL: {
			//system call 
			//cprintf("dispatched to sys call\n");	
			ret = syscall(tf->tf_regs.reg_eax,tf->tf_regs.reg_edx, tf->tf_regs.reg_ecx, tf->tf_regs.reg_ebx, tf->tf_regs.reg_edi, tf->tf_regs.reg_esi);
			tf->tf_regs.reg_eax = ret;
			
			return;
		}
	case IRQ_OFFSET + IRQ_TIMER: {  //clock interrupt
			//cprintf("a clock interrupt occurred -- we were at eip %x \n", tf->tf_eip);
			sched_yield();
			break; //just in case
	}
	
	default: {cprintf("dispatch:default\n"); }
	}

	

	// Handle keyboard interrupts.
	// LAB 5: Your code here.

	// Unexpected trap: The user process or the kernel has a bug.
	print_trapframe(tf);
	if (tf->tf_cs == GD_KT)
		panic("unhandled trap in kernel");
	else {
		env_destroy(curenv);
		return;
	}
}

void
trap(struct Trapframe *tf)
{
	
	
	if ((tf->tf_cs & 3) == 3) {
		// Trapped from user mode.
		// Copy trap frame (which is currently on the stack)
		// into 'curenv->env_tf', so that running the environment
		// will restart at the trap point.
		assert(curenv);
		curenv->env_tf = *tf;
		// The trapframe on the stack should be ignored from here on.
		tf = &curenv->env_tf;
	}


	// Dispatch based on what type of trap occurred
	trap_dispatch(tf);

	// If we made it to this point, then no other environment was
	// scheduled, so we should return to the current environment
	// if doing so makes sense.
	if (curenv && curenv->env_status == ENV_RUNNABLE)
		env_run(curenv);
	else
		sched_yield();
}

//after mypush va points to the last value pushed
void mypush(uintptr_t * vaddr, uint32_t value) {
	*vaddr = *vaddr - 4;
	uint32_t * memaddr = (uint32_t *)*vaddr;
	
	*memaddr = value;
	
}
void
page_fault_handler(struct Trapframe *tf)
{
	uint32_t fault_va;

	

	// Read processor's CR2 register to find the faulting address
	fault_va = rcr2();
	
	//cprintf("the faulting eip is %x and faulting address %x \n", tf->tf_eip, fault_va);

	// Handle kernel-mode page faults.
	
	// LAB 3: Your code here.
	
	if ((tf->tf_cs & 3) == 0) {
		print_trapframe(tf);
		panic("page fault in kernel mode!\n");
	}
	

	// We've already handled kernel-mode exceptions, so if we get here,
	// the page fault happened in user mode.

	// Call the environment's page fault upcall, if one exists.  Set up a
	// page fault stack frame on the user exception stack (below
	// UXSTACKTOP), then branch to curenv->env_pgfault_upcall.
	//
	// The page fault upcall might cause another page fault, in which case
	// we branch to the page fault upcall recursively, pushing another
	// page fault stack frame on top of the user exception stack.
	//
	// The trap handler needs one word of scratch space at the top of the
	// trap-time stack in order to return.  In the non-recursive case, we
	// don't have to worry about this because the top of the regular user
	// stack is free.  In the recursive case, this means we have to leave
	// an extra word between the current top of the exception stack and
	// the new stack frame because the exception stack _is_ the trap-time
	// stack.
	//
	// If there's no page fault upcall, the environment didn't allocate a
	// page for its exception stack, or the exception stack overflows,
	// then destroy the environment that caused the fault.
	//
	// Hints:
	//   user_mem_assert() and env_run() are useful here.
	//   To change what the user environment runs, modify 'curenv->env_tf'
	//   (the 'tf' variable points at 'curenv->env_tf').
	//cprintf("before curenve check\n");

	// LAB 4: Your code here.
	if (curenv->env_pgfault_upcall == NULL) {
		// Destroy the environment that caused the fault.
		cprintf("[%08x] user fault va %08x ip %08x\n",
		curenv->env_id, fault_va, tf->tf_eip);
		print_trapframe(tf);
		cprintf("DESTROYing environment because there is not handler registered!\n");
		env_destroy(curenv);
	}
	//cprintf("after curenv check\n");
	//we have a handler

	//we have a page for the exception stack
	//cprintf("entry in page table for entry mem is %x\n", *pgdir_walk(curenv->pgdir,XX USTACKTOP, 0));
	//check if we are on a recursive exception handling
	if ((tf->tf_esp >= UXSTACKTOP-PGSIZE) && (tf->tf_esp <= UXSTACKTOP-1)) {
		//yes, it is recursive
		cprintf("recursive, before assert, esp = %x, UXSTACKTOP = %x, UXSTACKTOP_PGSIZE= %x!\n", tf->tf_esp, UXSTACKTOP, UXSTACKTOP-PGSIZE );		
		user_mem_assert(curenv, (void *)tf->tf_esp-sizeof(struct UTrapframe), sizeof(struct UTrapframe) , PTE_P | PTE_U | PTE_W);
		cprintf("after assert, is future utf going to go under limit? %d \n", tf->tf_esp-sizeof(struct UTrapframe) < UXSTACKTOP - PGSIZE);
		//check if enough space left
		if (tf->tf_esp - (UXSTACKTOP - PGSIZE) < sizeof(struct UTrapframe) + 4 ) {
			// Destroy the environment that caused the fault.
			cprintf("[%08x] user fault va %08x ip %08x\n",
			curenv->env_id, fault_va, tf->tf_eip);
			cprintf("killing because out of space\n");
			print_trapframe(tf);
			env_destroy(curenv);
		}
		
		//cprintf("check aa\n");
		uintptr_t pva = tf->tf_esp;
		//push a space
		mypush(&pva, 0);

		mypush(&pva, tf->tf_esp);
		mypush(&pva, tf->tf_eflags);
		mypush(&pva, tf->tf_eip);
		mypush(&pva, tf->tf_regs.reg_eax);
		mypush(&pva, tf->tf_regs.reg_ecx);
		mypush(&pva, tf->tf_regs.reg_edx);
		mypush(&pva, tf->tf_regs.reg_ebx);
		mypush(&pva, tf->tf_regs.reg_oesp);
		mypush(&pva, tf->tf_regs.reg_ebp);
		mypush(&pva, tf->tf_regs.reg_esi);
		mypush(&pva, tf->tf_regs.reg_edi);
		mypush(&pva, tf->tf_err);
		mypush(&pva, fault_va);
		

		//cprintf("check bb\n");
		//switch the stack
		tf->tf_esp = pva;
		//prepare to call the user page fault handler
		tf->tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
		//tf->tf_ss the same?
		//cprintf("check cc\n");
		env_run(curenv); 	
		
	} else {
		//no, it is not recursive
        //cprintf("not recursive, before assert\n");
        user_mem_assert(curenv, (void *)UXSTACKTOP-sizeof(struct UTrapframe), sizeof(struct UTrapframe) , PTE_P | PTE_U | PTE_W);
		assert(tf->tf_esp < USTACKTOP);
        //cprintf("after assert\n");
		uintptr_t pva = UXSTACKTOP;
		//cprintf("check 11 \n");
		mypush(&pva, tf->tf_esp);
		mypush(&pva, tf->tf_eflags);
		mypush(&pva, tf->tf_eip);
		mypush(&pva, tf->tf_regs.reg_eax);
		mypush(&pva, tf->tf_regs.reg_ecx);
		mypush(&pva, tf->tf_regs.reg_edx);
		mypush(&pva, tf->tf_regs.reg_ebx);
		mypush(&pva, tf->tf_regs.reg_oesp);
		mypush(&pva, tf->tf_regs.reg_ebp);
		mypush(&pva, tf->tf_regs.reg_esi);
		mypush(&pva, tf->tf_regs.reg_edi);
		mypush(&pva, tf->tf_err);
		mypush(&pva, fault_va);
		//cprintf("check 22 \n");		
		//switch the stack
		tf->tf_esp = pva;
		//prepare to call the user page fault handler
		tf->tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
		//tf->tf_ss the same?
		//cprintf("check 33 \n");
		env_run(curenv); 	
	}
	
	
}

