use serde::{Deserialize, Serialize};
use crate::config::default::{DEFAULT_IO_MAP, DEFAULT_MEMORY_MAP};
use crate::memory::mmu::MMU;
use crate::memory::PhysAddr;
use crate::processor::processor::Emulator;

mod default;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

#[repr(u32)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum RegionType {
    Reserved,
    Bootloader,
    Kernel,
    KernelTables,
    Usable,
    MMIO,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct Region {
    pub base: PhysAddr,
    pub size: u32,
    pub region_type: RegionType,

    // flags for the ui
    #[serde(default)]
    pub identity_map_on_boot: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MemoryMap {
    pub table_addr: PhysAddr,
    pub regions: Vec<Region>,
}

#[derive(Serialize, Deserialize)]
pub struct IoMapping {
    pub device: String,
    pub base: u32,
    pub size: u32,
}

#[derive(Serialize, Deserialize)]
pub struct IoMap { items: Vec<IoMapping> }

impl Default for IoMap {
    fn default() -> Self {
        ron::from_str(DEFAULT_IO_MAP).unwrap()
    }
}

impl Default for MemoryMap {
    fn default() -> Self {
        ron::from_str(DEFAULT_MEMORY_MAP).unwrap()
    }
}

impl MemoryMap {
    pub const DEFAULT_MEMORY_MAP_ADDR: PhysAddr = 0x1000;

    pub fn new() -> Self {
        MemoryMap {
            table_addr: Self::DEFAULT_MEMORY_MAP_ADDR,
            regions: Vec::new(),
        }
    }

    pub fn apply(&self, cpu: &mut Emulator) {
        for reg in &self.regions {
            if reg.identity_map_on_boot {
                Self::identity_map(cpu.mmu_mut(), reg);
            }
        }

        let mem = cpu.memory_mut();
        let mut offset = self.table_addr;
        for region in &self.regions {
            println!("Wrote entry: {region:?} to page table");
            mem.write_word(offset, region.base);
            mem.write_word(offset + 4, region.size);
            mem.write_word(offset + 8, region.region_type as u32);
            offset += 12;
        }
    }

    pub fn identity_map(mmu: &mut MMU, region: &Region) {
        let first_page = region.base >> 12;
        let page_count = region.size >> 12;

        for page in (first_page..first_page + page_count).map(|p| p << 12) {
            mmu.create_mapping(page, page);
        }
    }
}
