#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>
#include <kern/kdebug.h>
#include <inc/string.h>

static struct Taskstate ts;
int last_line = -1;
  
/* 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)";
}


void
idt_init(void)
{
	extern struct Segdesc gdt[];
	
	// LAB 3: Your code here.
    extern int idtptr[];
  int i;
  for(i = 0; i < 3; i++)
    SETGATE(idt[i],1,GD_KT,idtptr[i],0);
  //breakpoint fault
  SETGATE(idt[3],0,GD_KT,idtptr[3],3);
  for(i = 4; i < 32; i++)
      SETGATE(idt[i],1,GD_KT,idtptr[i],0);
  for(; i < 48; i++)
    SETGATE(idt[i],0,GD_KT,idtptr[i],3);
  SETGATE(idt[48],0,GD_KT,idtptr[48],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.
    if(tf->tf_trapno == T_PGFLT)
        page_fault_handler(tf);
	
	  //breakpoint
  if(tf->tf_trapno == T_BRKPT) {
      //monitor(tf);
      if (curenv->env_debug) {
          curenv->env_tf.tf_eflags = curenv->env_tf.tf_eflags | 0x100;
      }
      return;      
  }

  //debug
  if(tf->tf_trapno == T_DEBUG) {
      //print_trapframe(tf);
      //cprintf("next instruction at %08x\n",tf->tf_eip);
      
      //monitor(tf);
      char *buf;
      struct Eipdebuginfo info;
      int ret = debuginfo_eip(tf->tf_eip, &info);
      //cprintf("line number: %d\nfile name: %s\nfunction: %s\nfunc addr: %08x\n", 
      //        info.eip_line, info.eip_file, info.eip_fn_name, info.eip_fn_addr);
      if ((last_line == -1) || (last_line == info.eip_line) || 
          (info.eip_line == 0)){
          last_line = info.eip_line;
          return;
      } else {
          last_line = -1;
          //buf = char[1024];
          //memset(buf,0,1024);
          
          tf->tf_eflags &= ~(0x100);
          return_to_debugger(info.eip_line);
          return;
      }
  }

  //syscalls
  if(tf->tf_trapno == T_SYSCALL){
      struct PushRegs *r = &(tf->tf_regs);
      //cprintf("sys call: %08x\n",r->reg_eax);
      r->reg_eax = syscall(r->reg_eax,r->reg_edx,r->reg_ecx,r->reg_ebx,r->reg_edi,r->reg_esi);
      return;
  }
  
	
	// Handle clock and serial interrupts.
	// LAB 4: Your code here.

  //interrupt
  if(tf->tf_trapno == IRQ_OFFSET+IRQ_TIMER) {
      sched_yield();
      return;
  }
  
  if(tf->tf_trapno == IRQ_OFFSET+IRQ_KBD) {
      kbd_intr();
      return;
  }
  // 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;
	}
	//cprintf("trap no: %08x\n",tf->tf_trapno);
	
	// 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();
}


void
page_fault_handler(struct Trapframe *tf)
{
	uint32_t fault_va;

	// Read processor's CR2 register to find the faulting address
	fault_va = rcr2();

	// Handle kernel-mode page faults.
	
	// LAB 3: Your code here.
    if((tf->tf_cs & 3) == 0) 
        panic("page fault in the kernel at %08x",fault_va);
	// 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').
	
	// LAB 4: Your code here.

    //cprintf("esp = %08x\n",tf->tf_esp);
    
    uintptr_t upcall = (uint32_t) curenv->env_pgfault_upcall;
    uintptr_t esp = (uint32_t) tf->tf_esp;
    
    //if not already on the user exception stack, start esp at the top
    if ((esp >= UXSTACKTOP) || (esp < (UXSTACKTOP-PGSIZE))) {
        esp = UXSTACKTOP;
    }
    //cprintf("page fault at %08x\n",rcr2());
        
    //if there is a registered upcall and space is available on the user stack
    if (upcall != 0) {
        //cprintf("upcall exists\n");
        //check page exists
        user_mem_assert(curenv, (void *) (esp-4-sizeof(struct UTrapframe)), sizeof(struct UTrapframe), PTE_W|PTE_U|PTE_P);
        if ((esp-4-sizeof(struct UTrapframe)) > (UXSTACKTOP-PGSIZE)) {
            // there's space on the stack
            struct UTrapframe *utf = (struct UTrapframe *) (esp-4-sizeof(struct UTrapframe));
            
            //fill in user trapframe
            utf->utf_fault_va = rcr2();
            utf->utf_err = tf->tf_err;
            utf->utf_regs = tf->tf_regs;
            utf->utf_eip = tf->tf_eip;
            utf->utf_eflags = tf->tf_eflags;
            utf->utf_esp = tf->tf_esp-4;
        
            //set up eip and esp for userspace page fault handler
            //print_trapframe(tf);
            //cprintf("trap utf = %08x\n",utf);
            tf->tf_esp = (uintptr_t) utf;
            //cprintf("trap upcall = %08x\n",upcall);
            tf->tf_eip = upcall;
            env_run(curenv);
        }   
    }
	// Destroy the environment that caused the fault.
    struct Eipdebuginfo info;
    debuginfo_eip(tf->tf_eip,&info);
    //cprintf("upcall: %08x\n",upcall);
        
    //cprintf("file: %s line: %d\n",info.eip_file, info.eip_line);

	cprintf("[%08x] user fault va %08x ip %08x\n",
		curenv->env_id, fault_va, tf->tf_eip);
	print_trapframe(tf);
	env_destroy(curenv);
}

