use crate::VarValAddr;
use crate::{Arc, BuckMap, BuckMapInfo, Data, DataType, Rc, make_obj_dt};

use page_store::*;

/// returned by [PageSet::load].
#[derive(Default)]
pub struct PData {
    pub data: Data,
    pnum: u64,
    pub changed: bool,
    /// Causes panic if [PageSet::note] is not called (debug diagnostic).
    no_drop: bool,
}

impl PData {
    pub fn new(pnum: u64, data: Data, changed: bool) -> Self {
        Self {
            pnum,
            data,
            changed,
            no_drop: true,
        }
    }
    pub fn take_data(&mut self) -> Data {
        std::mem::take(&mut self.data)
    }
    pub fn make_mut(&mut self) -> &mut Vec<u8> {
        Arc::make_mut(&mut self.data)
    }
}

impl Drop for PData {
    fn drop(&mut self) {
        assert!(!self.no_drop); // Could be debug_assert eventually, but leave for now.
    }
}

/// Set of working pages, clean and changed.
pub struct PageSet {
    wapd: AccessPagedData,
    clean: HashMap<u64, Data>,
    changed: HashMap<u64, Data>,

    /// Variable length value storage data.
    pub vvs: (u64, u64),

    /// DataType of System Object
    pub obj_dt: Rc<DataType>,

    /// Next object id
    pub next_obj_id: i64,

    /// System Object Map
    pub obj_map: BuckMapInfo,

    /// Maps object names to object ids
    pub obj_by_name: BuckMapInfo,

    /// Maps table ids to datatypes
    pub table_datatype_map: BuckMapInfo,
}

impl PageSet {
    pub fn new(wapd: AccessPagedData) -> Self {
        // If not a new database, root1 and other values need to be restored somehow.
        let root1 = wapd.alloc_page() + 1;
        let mut ps = Self {
            wapd,
            clean: HashMap::default(),
            changed: HashMap::default(),
            vvs: (root1, 0),
            next_obj_id: 0,
            obj_map: BuckMapInfo::default(),
            obj_by_name: BuckMapInfo::default(),
            table_datatype_map: BuckMapInfo::default(),
            obj_dt: Rc::new(make_obj_dt()),
        };
        ps.obj_map = BuckMap::<VarValAddr>::new(1, &mut ps).save();
        ps.obj_by_name = BuckMap::<VarValAddr>::new(1, &mut ps).save();
        ps.table_datatype_map = BuckMap::<VarValAddr>::new(1, &mut ps).save();
        ps
    }

    pub fn compute_size(&self, size: usize) -> usize {
        let psi = &self.wapd.spd.psi;
        if size > psi.max_size_page() {
            return 0;
        }
        let ix = psi.index(size);
        psi.size(ix)
    }

    pub fn new_page(&self) -> u64 {
        self.wapd.alloc_page() + 1 // Add 1 so that zero can be used as null page.
    }

    pub fn free_page(&mut self, pnum: u64) {
        self.clean.remove(&pnum);
        self.changed.remove(&pnum);
        self.wapd.free_page(pnum - 1);
    }

    /// Loads page data, the PData must later be returned by calling note.
    pub fn load(&mut self, pnum: u64) -> PData {
        if pnum == 0 {
            PData::default() // Convenient for some read operations that read unitialised pages.
        } else if let Some(data) = self.clean.remove(&pnum) {
            PData::new(pnum, data, false)
        } else if let Some(data) = self.changed.remove(&pnum) {
            PData::new(pnum, data, true)
        } else {
            let data = self.wapd.get_data(pnum - 1);
            PData::new(pnum, data, false)
        }
    }

    /// Returns page loaded page data.
    pub fn note(&mut self, mut pd: PData) {
        if pd.pnum == 0 {
            // Do nothing
        } else if pd.changed {
            self.changed.insert(pd.pnum, pd.take_data());
        } else {
            self.clean.insert(pd.pnum, pd.take_data());
        }
        pd.no_drop = false;
    }

    /// Saave all the changed pages.
    pub fn save(&mut self) {
        for (pnum, data) in self.changed.drain() {
            println!("Saving data pnum={} len={}", pnum, data.len());
            self.wapd.set_data(pnum - 1, data);
        }
        self.wapd.save(SaveOp::Save);
    }
}
