use crate::config::{kernel_stack_position, TRAP_CONTEXT};
use crate::mm::address::{PhysPageNum, VirtAddr};
use crate::mm::memory_set::{KERNEL_SPACE, MapPermission, MemorySet};
use crate::println;
use crate::task::context::{TaskContext};
use crate::trap::{trap_handler, TrapContext};

// TCB的字段, 用来保存任务的状态
#[derive(Copy, Clone, PartialEq)]
pub enum TaskStatus {
    UnInit,  // 未初始化
    Ready,  // 准备运行
    Running,  // 正在运行
    Exited,  // 已退出
}


// 一个任务的主体, 用来保存或者控制一个任务所有需要的东西
pub struct TaskControlBlock {
    pub user_time: usize,  // 用户态程序用的时间
    pub kernel_time: usize,  // 内核态程序所用的时间
    pub task_status: TaskStatus,
    pub task_cx: TaskContext,
    pub memory_set: MemorySet,  // tcb他自己的地址空间
    pub trap_cx_ppn: PhysPageNum,  // tcb访问 trap context所在的真实的物理页, 它对应逻辑页的次高页
    pub base_size: usize,  // 应用地址空间中从0x0开始到用户栈结束一共包含多少字节, 就是用户数据有多大
    pub heap_bottom: usize,  // 堆的起始地址 这个一开始和base_size 是一样的
    pub program_brk: usize,  // 这个一开始和base_size 是一样的, 表示进程当前的堆边界, 即堆的顶部地址, 它指向堆中最后一个已分配内存块的末尾, 下一个内存分配将从该地址开始
    //   base_size(低地址) --- TRAP_CONTEXT(次高页) 的虚拟空间, 可以当做堆来使用
}

impl TaskControlBlock {
    pub fn from(elf_data: &[u8], app_id: usize) -> Self {
        // memory_set with elf program headers/trampoline/trap context/user stack
        // 返回 内存空间, 用户栈的栈底和当前栈顶, 和入口地址
        let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);

        // 在当前的地址空间中, 找到 trap_context 逻辑页 所在的真实物理页, 他逻辑地址在次高页, 真实物理地址是我们在 from_elf 申请映射的未知地址
        // 这个真实的物理地址, 我们后续再陷入的时候会用到, 进行强写数据
        let trap_cx_ppn = memory_set
            .page_table
            .get_pte(VirtAddr::from(TRAP_CONTEXT).into())  // 虚拟地址次高页, 所在对应的物理内存的pte,
            .unwrap()
            .ppn();  // pte 内的ppn, 得到实际 虚拟内存次高页 所在的物理页号

        // 初始化任务状态
        let task_status = TaskStatus::Ready;
        // map a kernel-stack in kernel space
        // 根据 app_id 创建受保护的用户的任务内核栈, 并得到栈的起始地址和结束地址, 这个在次高页的下面的 某个位置
        // kernel_stack_bottom = kernel_stack_top - PAGESIZE * 2
        // kernel_stack_top 是当前栈的栈顶, 同时也是现在没有push操作也是栈底
        // 这里都是虚拟地址
        let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(app_id);

        // 把用户的内核栈逻辑段, 映射并创建出来实际的物理页帧, 到内核地址空间, 供内核态可以进行寻址找到用户应用的内核栈,
        // 这里使用虚拟映射  而不是恒等映射, 是因为需要动态调整?
        // 这个只有在内核会用到
        KERNEL_SPACE.exclusive_access().insert_framed_area(
            kernel_stack_bottom.into(),
            kernel_stack_top.into(),
            MapPermission::R | MapPermission::W,
        );
        // 构建 任务控制块
        let task_control_block = Self {
            user_time: 0,
            kernel_time: 0,
            task_status,
            task_cx: TaskContext::goto_trap_return(kernel_stack_top),  // 根据内核栈构造任务切换上下文, 并把 switch任务切换的 ra 设置为 trap_return的函数地址
            memory_set,  // 新增, 当前任务的地址空间
            trap_cx_ppn,  // 新增  逻辑次高页的trap context, 对应的这个是真实的物理页, 我们这里保存一份, 省的在memory_set 里面查找了
            base_size: user_sp,  // 用户栈顶以下 是代码中的各种逻辑段+栈段, 应用地址空间中从0x0开始到用户栈结束一共包含多少字节, 所以大小就是截止到user_sp
            heap_bottom: user_sp,  // todo 这里书里没写干嘛的 但是 user_sp 这个是用户空间的初始栈顶位置 指向最后的用户段中用户栈的高地址栈底的位置 在往上是空的空间了
            program_brk: user_sp,  // 同上
        };
        // prepare TrapContext in user space
        // 根据 trap_cx_ppn 构建 陷入 trap context 的结构体
        let trap_cx = task_control_block.get_trap_cx();  // 根据trap_cx_ppn得到真实的物理页的 trap context的地址
        *trap_cx = TrapContext::from(  // 对 tcb 的 TrapContext 的物理页进行 修改,
           entry_point,  // 在陷入完成后, 准备返回用户态执行的, 用户代码入口地址
           user_sp,  // 用户栈, 这个栈的地址是虚拟地址
           KERNEL_SPACE.exclusive_access().token(),  // 内核 satp 页表的寄存器信息
           kernel_stack_top,  // 用户应用的内核栈顶  在内核空间的位置 (这个是根据appid 计算出来的, 也只能在内核的地址空间中才能根据这个地址 看到相关的栈)
           trap_handler as usize,  // 内核中 trap handler的入口点虚拟地址(恒等映射所以 这里也能找到)
        );
        task_control_block
        // 以上:
        // 任务控制块和 trap控制块, 分别多了一些字段
        // trap控制块(这个每次陷入都会被 sscratch 寄存器临时保存):
        //     多的三个字段 首次写入后, 后续只会读取和恢复,不会被再次写入新值, 这是为了
        //     kernel_satp 内核空间页表所在位置的实际物理地址, 这个值以后会在切换的时候替换到satp寄存器
        //     kernel_sp, 这个保存这个应用 他 根据appid计算出来 所持有的任务内核栈, 在jump context之前 会恢复
        //     trap_handler 这是在内核在陷入的时候, 我们自定义的 trap_handle 的地址, 在陷入保存完寄存器之后, 需要 jmp 到这个函数地址
        // task 控制块 TCB, 这个是在内核进行 switch时的上下文结构:
        //     memory_set 表示当前任务他的地址空间
        //     trap_cx_ppn 用来保存 上方 trap控制块的 实际的物理页帧, 在用户空间中都被应设在了TRAP_CONTEXT相关的次高位置虚拟地址处, 在内核空间我们这保存一份实际的物理地址, 在内核空间读取这个通过恒等映射就得到任务的 trap context
        //     base_size 用户栈顶距离0x0的距离, 即用户应用已知的大小
        //
    }

    // 扩张/缩减, 当前应用的地址空间中的堆段, 指定字节的数据
    pub fn change_program_brk(&mut self, size: i32) -> Option<usize> {
        // 堆顶
        let old_break = self.program_brk;
        // 申请后新的堆顶
        let new_brk = self.program_brk as isize + size as isize;

        // 如果新的堆顶 小于 堆底 直接返回(防止过度释放)
        if new_brk < self.heap_bottom as isize {
            return None;
        }

        // 如果是小于0 说明需要释放,缩小堆的大小
        // 传入两个参数 堆底虚拟地址, 和 新的堆顶地址
        let result = if size < 0 {
            self.memory_set
                .shrink_to(VirtAddr(self.heap_bottom), VirtAddr(new_brk as usize))
        } else {
            self.memory_set
                .append_to(VirtAddr(self.heap_bottom), VirtAddr(new_brk as usize))
        };
        println!("size: {}, old_break: {}", size, old_break);
        // 如果成功就把新的堆顶的地址, 复制到结构提上记录, 并把旧的堆顶返回
        if result {
            self.program_brk = new_brk as usize;
            Some(old_break)
        } else {
            None
        }
    }
}

impl TaskControlBlock {
    // 根据 trap context 的实际的物理地址, 强转为 TrapContext 结构体
    pub fn get_trap_cx(&self) -> &'static mut TrapContext {
        self.trap_cx_ppn.get_mut()
    }
    
    pub fn get_user_token(&self) -> usize {
        self.memory_set.token()
    }
}