use alloc::vec::Vec;
use lazy_static::lazy_static;
use crate::config::{KERNEL_STACK_SIZE, PAGE_SIZE, TRAMPOLINE};
use crate::mm::address::VirtAddr;
use crate::mm::memory_set::{KERNEL_SPACE, MapPermission};
use crate::sync::UPSafeCell;

// pid分配器
pub struct PidAllocator {
    current: usize,  // 当前分配到多少号了
    recycled: Vec<usize>,  // 被回收的pid, 后续可以再次利用
}

// 全局的pid分配器
lazy_static! {
    pub static ref PID_ALLOCATOR: UPSafeCell<PidAllocator> =
        unsafe { UPSafeCell::new(PidAllocator::new()) };
}

pub fn pid_alloc() -> PidHandle {
    PID_ALLOCATOR.exclusive_access().alloc()
}


impl PidAllocator {
    pub fn new() -> Self {
        Self {
            current: 0,
            recycled: Vec::new(),
        }
    }
}

impl PidAllocator {
    // 分配一个pid
    pub fn alloc(&mut self) -> PidHandle {
        if let Some(pid) = self.recycled.pop() {
            PidHandle(pid)
        } else {
            let current = self.current;
            self.current += 1;
            PidHandle(current)
        }
    }

    // 取消分配一个pid
    pub fn dealloc(&mut self, pid: usize) {
        assert!(pid < self.current);
        // 防止二次释放
        assert!(
            !self.recycled.iter().any(|tmp_pid| *tmp_pid == pid),
            "pid {} has been deallocated!",
            pid
        );

        self.recycled.push(pid);
    }
}


// 用来表示进程的唯一标识符, RAII思想来进行释放管理
pub struct PidHandle(pub usize);

// pid_handle 被drop时, 同时把pid 还给全局管理器
impl Drop for PidHandle {
    fn drop(&mut self) {
        PID_ALLOCATOR.exclusive_access().dealloc(self.0);
    }
}


/// 得到 "用户程序的内核栈" 的在 内核本身地址空间 中的虚拟位置(当然由于恒等映射其实也是实际的物理地址)
/// 这个在 跳板的下面的某个位置
/// 每个用户除了分配 KERNEL_STACK_SIZE 大小外, 还额外 增加 一个PAGE_SIZE 放在比栈底要高的地址 用来隔离每个用户内核栈
/// 根据 pid 在相应的位置, 得到app 内核栈的它处于的位置, 位置, 注意代码实现, 这个是有保护页面的(1 pagesize),
/// 在逻辑地址空间存在保护页面(保护页面不会被映射), 但是在物理地址空间,这个保护页会被随时被物理页帧管理器分配走
/// 返回的 top是内存的高地址, bottom是内存的低地址
pub fn kernel_stack_position(pid: usize) -> (usize, usize) {
    let top = TRAMPOLINE - pid * (KERNEL_STACK_SIZE + PAGE_SIZE);
    // 栈底只需要 KERNEL_STACK_SIZE 即可,
    let bottom = top - KERNEL_STACK_SIZE;
    (bottom, top)
}

// 新的结构体, 内核栈, 之前我们是根据 app_id 找到的, 现在我们需要改成 统一分配的pid, 根据pid摆放应用内核栈的位置
pub struct KernelStack {
    pid: usize,
}

impl KernelStack {
    // push 一个 泛型, 到 内核栈中, 直接减去T的大小, 再写入即可
    pub fn push_on_top<T: Sized>(&self, value: T) -> *mut T {
        let kernel_stack_top = self.get_top();
        let kernel_stack_top = self.get_top();
        let ptr_mut = (kernel_stack_top - core::mem::size_of::<T>()) as *mut T;
        unsafe {
            *ptr_mut = value;
        }
        ptr_mut
    }

    // 得到当前内核栈的 栈底(高地址)
    pub fn get_top(&self) -> usize {
        let (_, kernel_stack_top) = kernel_stack_position(self.pid);
        kernel_stack_top
    }
}

impl KernelStack {
    // 根据 pid handle 在内核空间(最高页的下面) 某个位置, 创建一个 逻辑段 作为应用的内核栈
    pub fn from(pid_handle: &PidHandle) -> Self {
        let pid = pid_handle.0;

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

        // 把用户的内核栈逻辑段, 映射并创建出来实际的物理页帧, 到内核地址空间, 供内核态可以进行寻址找到用户应用的内核栈,
        // 这里使用虚拟映射  而不是恒等映射, 是因为需要动态调整?
        // 这个只有在内核会用到
        KERNEL_SPACE
            .exclusive_access()
            .insert_framed_area(
                kernel_stack_bottom.into(),
                kernel_stack_top.into(),
                MapPermission::R | MapPermission::W,
            );
        KernelStack { pid: pid_handle.0 }
    }
}


// 当kernel stack 生命周期结束, 对应 new 出来的一个 应用内核栈 逻辑段也随之删除
// 注意! 一般这个是在 wait_pid 内用的, 用于父进程回收子进程资源时, 子进程内的字段根据RAII执行
impl Drop for KernelStack {
    fn drop(&mut self) {
        let (kernel_stack_bottom, _) = kernel_stack_position(self.pid);
        let kernel_stack_bottom_va: VirtAddr = kernel_stack_bottom.into();
        KERNEL_SPACE
            .exclusive_access()
            .remove_area_with_start_vpn(kernel_stack_bottom_va.into());
    }
}




