#[derive(Debug, Clone, Default)]
#[repr(C)]
pub struct TrapFrame {
    // Pushed by __alltraps at 'trap.asm'
    pub r15: usize,
    pub r14: usize,
    pub r13: usize,
    pub r12: usize,
    pub rbp: usize,
    pub rbx: usize,

    pub r11: usize,
    pub r10: usize,
    pub r9: usize,
    pub r8: usize,
    pub rsi: usize,
    pub rdi: usize,
    pub rdx: usize,
    pub rcx: usize,
    pub rax: usize,

    // Pushed by vector{i} at 'vector.asm'
    pub trap_num: usize,
    pub error_code: usize,

    // Pushed by CPU
    pub rip: usize,
    pub cs: usize,
    pub rflags: usize,

    // Pushed by CPU when Ring3->0
    pub rsp: usize,
    pub ss: usize,
}

/// 用于在内核栈中构造新线程的中断帧
impl TrapFrame {
    fn new_kernel_thread(entry: extern fn(usize) -> !, arg: usize, rsp: usize) -> Self {
        use crate::arch::gdt;
        let mut tf = TrapFrame::default();
        tf.rdi = arg;
        tf.cs = gdt::KCODE_SELECTOR.0 as usize;
        tf.rip = entry as usize;
        tf.ss = gdt::KDATA_SELECTOR.0 as usize;
        tf.rsp = rsp;
        tf.rflags = 0x282;
        tf
    }
    fn new_user_thread(entry_addr: usize, rsp: usize, is32: bool) -> Self {
        use crate::arch::gdt;
        let mut tf = TrapFrame::default();
        tf.cs = if is32 { gdt::UCODE32_SELECTOR.0 } else { gdt::UCODE_SELECTOR.0 } as usize;
        tf.rip = entry_addr;
        tf.ss = if is32 { gdt::UDATA32_SELECTOR.0 } else { gdt::UDATA_SELECTOR.0 } as usize;
        tf.rsp = rsp;
        tf.rflags = 0x282;
        tf
    }
    pub fn is_user(&self) -> bool {
        self.cs & 0x3 == 0x3
    }
}

#[derive(Debug, Default)]
#[repr(C)]
struct ContextData {
    cr3: usize,
    r15: usize,
    r14: usize,
    r13: usize,
    r12: usize,
    rbp: usize,
    rbx: usize,
    rip: usize,
}

impl ContextData {
    fn new(cr3: usize) -> Self {
        ContextData { rip: forkret as usize, cr3, ..ContextData::default() }
    }
}

/// 新线程的内核栈初始内容
#[derive(Debug)]
#[repr(C)]
struct InitStack {
    context: ContextData,
    trapret: usize,
    tf: TrapFrame,
}

impl InitStack {
    unsafe fn push_at(self, stack_top: usize) -> Context {
        let ptr = (stack_top as *mut Self).offset(-1);
        *ptr = self;
        Context(ptr as usize)
    }
}

extern {
    fn trap_ret();
}

/// The entry point of new thread
extern fn forkret() {
    // Will return to `trapret`
}

#[derive(Debug)]
pub struct Context(usize);

impl Context {
    /// Switch to another kernel thread.
    ///
    /// Defined in `trap.asm`.
    ///
    /// Push all callee-saved registers at the current kernel stack.
    /// Store current rsp, switch to target.
    /// Pop all callee-saved registers, then return to the target.
    #[naked]
    #[inline(never)]
    pub unsafe extern fn switch(&mut self, target: &mut Self) {
        asm!(
        "
        // push rip (by caller)

        // Save old callee-save registers
        push rbx
        push rbp
        push r12
        push r13
        push r14
        push r15
        mov r15, cr3
        push r15

        // Switch stacks
        mov [rdi], rsp      // rdi = from_rsp
        mov rsp, [rsi]      // rsi = to_rsp

        // Save old callee-save registers
        pop r15
        mov cr3, r15
        pop r15
        pop r14
        pop r13
        pop r12
        pop rbp
        pop rbx

        // pop rip
        ret"
        : : : : "intel" "volatile" )
    }

    pub unsafe fn null() -> Self {
        Context(0)
    }

    pub unsafe fn new_kernel_thread(entry: extern fn(usize) -> !, arg: usize, kstack_top: usize, cr3: usize) -> Self {
        InitStack {
            context: ContextData::new(cr3),
            trapret: trap_ret as usize,
            tf: TrapFrame::new_kernel_thread(entry, arg, kstack_top),
        }.push_at(kstack_top)
    }
    pub unsafe fn new_user_thread(entry_addr: usize, ustack_top: usize, kstack_top: usize, is32: bool, cr3: usize) -> Self {
        InitStack {
            context: ContextData::new(cr3),
            trapret: trap_ret as usize,
            tf: TrapFrame::new_user_thread(entry_addr, ustack_top, is32),
        }.push_at(kstack_top)
    }
    pub unsafe fn new_fork(tf: &TrapFrame, kstack_top: usize, cr3: usize) -> Self {
        InitStack {
            context: ContextData::new(cr3),
            trapret: trap_ret as usize,
            tf: {
                let mut tf = tf.clone();
                tf.rax = 0;
                tf
            },
        }.push_at(kstack_top)
    }
    /// Called at a new user context
    /// To get the init TrapFrame in sys_exec
    pub unsafe fn get_init_tf(&self) -> TrapFrame {
        (*(self.0 as *const InitStack)).tf.clone()
    }
}