use alloc::vec;
use alloc::vec::Vec;
use bitflags::*;
use crate::config::TRAMPOLINE;
use crate::mm::address::{PhysPageNum, VirtAddr, VirtPageNum};
use crate::mm::frame_allocator::{frame_alloc, FrameTracker};
use crate::println;
use crate::task::TASK_MANAGER;


bitflags! {
    pub struct PTEFlags: u8 {
        const V = 1 << 0;  // 仅当位 V 为 1 时，页表项才是合法的
        const R = 1 << 1;  // 可读标志
        const W = 1 << 2;  // 可写标志
        const X = 1 << 3;  // 可执行标志
        const U = 1 << 4;  // 是否允许U级访问
        const G = 1 << 5;  //
        const A = 1 << 6;  // 从上次清零之后, 该页是否被访问过
        const D = 1 << 7;  // 从上次清零之后, 该页是否被修改过
    }
}

#[derive(Copy, Clone)]
#[repr(C)]
/// pte结构体
///
/// 对应的64个字节的布局为
///
/// 10bit(保留) | 44bit(物理页号) | 10bit(标志位)
pub struct PageTableEntry {
    pub bits: usize,
}


impl PageTableEntry {
    // 从一个物理页号和一个flag 创建出来一个 PTE
    // 物理也好 左移10位 加上 flag 即可
    pub fn from(ppn: PhysPageNum, flags: PTEFlags) -> Self {
        PageTableEntry {
            bits: ppn.0 << 10 | flags.bits as usize,
        }
    }

    // 生成一个全0的PTE, V也是0, 它是不合法的
    pub fn new() -> Self {
        PageTableEntry { bits: 0 }
    }

    // 从PTE中的到 物理页的地址, 右移bit flag的宽度 再只保留44位即可得到 usize类型的物理页号, 随后调用into进行转换
    pub fn ppn(&self) -> PhysPageNum {
        (self.bits >> 10 & ((1usize << 44) - 1)).into()
    }
    // 从PTE中得到bit flag
    pub fn flags(&self) -> PTEFlags {
        PTEFlags::from_bits(self.bits as u8).unwrap()
    }

    // 判断是否合法, 只保留 V标志的位置, 再判断是否为0
    pub fn is_valid(&self) -> bool {
        (self.flags() & PTEFlags::V) != PTEFlags::empty()
    }
    // 判断是否可读
    pub fn readable(&self) -> bool {
        (self.flags() & PTEFlags::R) != PTEFlags::empty()
    }
    // 判断是否可写
    pub fn writable(&self) -> bool {
        (self.flags() & PTEFlags::W) != PTEFlags::empty()
    }
    // 判断是否可执行
    pub fn executable(&self) -> bool {
        (self.flags() & PTEFlags::X) != PTEFlags::empty()
    }
}

// 页表
pub struct PageTable {
    root_ppn: PhysPageNum,
    // 这里保存, 保存了 被映射出来的每级页表的物理帧, 页表被删除时, 页表所占用的物理帧 也被rust释放执行drop还给物理页帧管理器, FrameTracker 实现了 Drop trait
    frames: Vec<FrameTracker>,
}

impl PageTable {
    // 创建根节点页表
    pub fn new() -> Self {
        let frame = frame_alloc().unwrap();
        PageTable {
            root_ppn: frame.ppn,
            frames: vec![frame],
        }
    }

    // 从stap寄存器保存的特定格式的token, 转为根节点页表
    pub fn from_token(satp: usize) -> Self {
        Self {
            // 只保留44位 bit, 这44位bit, 就是 页表所在的物理页帧
            root_ppn: PhysPageNum::from(satp & ((1usize << 44) - 1)),
            frames: Vec::new(),
        }
    }

}

impl PageTable {
    // 得到该页表被设置到 stap 需要的格式
    // 注意, 这个只能是根节点才会用得到
    pub fn token(&self) -> usize {
        // 8 表示MODE, SV39分页机制开启
        // 4bit 16bit 44bit 构成页表的 stap CSR的格式要求
        8usize << 60 | self.root_ppn.0
    }

    // 寻找 指定页号, 如果不存在中间节点, 则创建出来, 知道找到三级页表的 指定下标 的 pte的指针
    fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
        let idxs = vpn.indexes();
        let mut ppn = self.root_ppn;
        let mut result: Option<&mut PageTableEntry> = None;
        for (i, idx) in idxs.iter().enumerate() {
            // 得到一级页表的pte
            let pte = &mut ppn.get_pte_array()[*idx];
            // 到达最后一级, 不管有无数据直接 跳出, 返回 三级页表保存的信息  0或者指向  物理页44bit+权限10bit 的数据
            if i == 2 {
                result = Some(pte);
                break;
            }
            // 如果没有中间节点
            if pte.is_valid() == false {
                let frame = frame_alloc().unwrap();
                *pte = PageTableEntry::from(frame.ppn, PTEFlags::V);
                self.frames.push(frame);
            }
            ppn = pte.ppn();
        }
        result
    }

    // 和 find_pte_create 差不多, 只不过这个不创建中间节点, 缺中间节点立马就返回
    fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
        let idxs = vpn.indexes();
        let mut ppn = self.root_ppn;
        let mut result: Option<&mut PageTableEntry> = None;
        for (i, idx) in idxs.iter().enumerate() {
            let pte = &mut ppn.get_pte_array()[*idx];
            if i == 2 {
                result = Some(pte);
                break;
            }
            if pte.is_valid() == false {
                return None;
            }
            ppn = pte.ppn();
        }
        result
    }

    // 把虚拟页 映射到页表中, 并创建一个 物理页, 然后把物理页ppn组成pte的格式, copy到 虚拟页对应的页表项内
    pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
        // 查找vpn 没有中间节点就创建出来
        let pte = self.find_pte_create(vpn).unwrap();
        assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn.0);
        *pte = PageTableEntry::from(ppn, flags | PTEFlags::V);
    }

    // 把虚拟页 取消映射(注意, 取消之后, 外部一定要 把该虚拟页对应的物理页帧 还给全局的物理页帧管理器)
    pub fn unmap(&mut self, vpn: VirtPageNum) {
        let pte = self.find_pte(vpn).unwrap();
        assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn.0);
        *pte = PageTableEntry::new();
    }

    // 根据虚拟页号, 找到页表中对应的pte
    pub fn get_pte(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
        self.find_pte(vpn).map(|pte| *pte)
    }
}

// 根据token得到用户应用的页表, 并根据ptr 和len 在页表中找到指定的数据
pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&'static mut [u8]> {
    // 根据寄存器token, 得到用户页表
    let page_table = PageTable::from_token(token);

    // 用户空间指定数据 的 逻辑起始位置
    let mut start = ptr as usize;
    // 用户空间指定数据的 逻辑结束位置
    let end = start + len;

    // 循环 逻辑的起始和结束位置, 根据页表,在物理页中寻找数据
    // 可能 start 和end 跨了很多页, 需要找到每一页并偏移
    let mut v = Vec::new();
    while start < end {
        // 逻辑起始位置对应实际的物理页帧
        let start_va = VirtAddr::from(start);
        let mut vpn = start_va.floor();
        let ppn = page_table.get_pte(vpn).unwrap().ppn();

        // 当前start页 的结束页 就是下一个逻辑页 start+1 的位置
        vpn = (vpn.0+1).into();
        let mut end_va: VirtAddr = vpn.into();

        // 找到 比较小的结束位置
        end_va = end_va.min(VirtAddr::from(end));

        // 如果 不是最后一页, 就找到当前物理页的所有4096字节 进行拷贝
        if end_va.page_offset() == 0 {
            v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..]);
        } else {
            // 如果是最后一页 就 拷贝到最后的位置
            v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..end_va.page_offset()]);
        }

        // 状态转移, 循环 拷贝下一个虚拟逻辑页
        start = end_va.into();
    }
    v
}


