use crate::{
    id::{RecRef, SegmentId},
    journal::{JournalId, records::FreedPage},
    snapshots::{SnapshotId, Snapshots},
};

use std::{
    collections::HashMap,
    sync::{Arc, Weak},
};

#[derive(Clone, Debug)]
pub struct SnapshotEntry {
    id: RecRef,
    case: EntryCase,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Change {
    pub pos: u64,
    pub version: u16,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum EntryCase {
    Change(Change),
    Insert,
}

impl SnapshotEntry {
    pub fn change(id: &RecRef, pos: u64, version: u16) -> SnapshotEntry {
        SnapshotEntry {
            id: *id,
            case: EntryCase::Change(Change { pos, version }),
        }
    }
    pub fn insert(id: &RecRef) -> SnapshotEntry {
        SnapshotEntry {
            id: *id,
            case: EntryCase::Insert,
        }
    }
    pub fn rec_id(&self) -> &RecRef {
        &self.id
    }
    pub(crate) fn case(&self) -> &EntryCase {
        &self.case
    }
}

#[derive(Clone, Debug)]
pub struct SegmentSnapshot {
    name: String,
    id: SegmentId,
    first_page: u64,
}
impl SegmentSnapshot {
    pub fn new(name: &str, id: SegmentId, first_page: u64) -> SegmentSnapshot {
        SegmentSnapshot {
            name: name.to_string(),
            id,
            first_page,
        }
    }
    pub(crate) fn name(&self) -> &str {
        &self.name
    }
    pub(crate) fn segment_id(&self) -> SegmentId {
        self.id
    }
    pub(crate) fn first_page(&self) -> u64 {
        self.first_page
    }
}
#[derive(Clone, Debug)]
pub struct CleanInfo {
    freed_pages: Vec<u64>,
    remove_page_address: Vec<(SegmentId, u64)>,
}
impl CleanInfo {
    pub(crate) fn new(freed_pages: Vec<FreedPage>, remove_page_address: Vec<(SegmentId, u64)>) -> Self {
        Self {
            freed_pages: freed_pages.iter().map(|x| x.page).collect(),
            remove_page_address,
        }
    }
    pub(crate) fn freed_pages(&self) -> &[u64] {
        &self.freed_pages
    }
    pub(crate) fn segment_pages(&self) -> &[(SegmentId, u64)] {
        &self.remove_page_address
    }
}

#[derive(Clone)]
pub(crate) struct PendingClean {
    ops: Weak<Snapshots>,
    journal_id: Option<JournalId>,
    freed_pages: Option<CleanInfo>,
}

impl PendingClean {
    fn new(ops: &Arc<Snapshots>, journal_id: Option<JournalId>, freed_pages: Option<CleanInfo>) -> Self {
        Self {
            ops: Arc::downgrade(ops),
            journal_id,
            freed_pages,
        }
    }
}
impl Drop for PendingClean {
    fn drop(&mut self) {
        if let Some(ops) = self.ops.upgrade() {
            ops.free_resources(&self.journal_id, &self.freed_pages).unwrap();
        }
    }
}

#[derive(Clone)]
pub struct SnapshotData {
    snapshot_id: SnapshotId,
    entries: Option<Vec<SnapshotEntry>>,
    journal_id: Option<JournalId>,
    freed_pages: Option<CleanInfo>,
    pending_clean: Option<Arc<PendingClean>>,
    segments: Option<HashMap<SegmentId, SegmentSnapshot>>,
    segments_name: Option<HashMap<String, SegmentSnapshot>>,
    reference_count: u32,
}

impl SnapshotData {
    pub(crate) fn new(id: SnapshotId, reference_count: u32) -> Self {
        Self {
            snapshot_id: id,
            entries: None,
            journal_id: None,
            freed_pages: None,
            pending_clean: None,
            segments: None,
            segments_name: None,
            reference_count,
        }
    }
    pub(crate) fn fill(
        &mut self,
        segs_id: HashMap<SegmentId, SegmentSnapshot>,
        segs_name: HashMap<String, SegmentSnapshot>,
    ) {
        self.segments = Some(segs_id);
        self.segments_name = Some(segs_name);
    }
    pub(crate) fn fill_records(&mut self, journal_id: JournalId, entries: Vec<SnapshotEntry>) {
        self.entries = Some(entries);
        self.journal_id = Some(journal_id);
    }

    pub(crate) fn fill_clean_info(&mut self, clean: CleanInfo) {
        self.freed_pages = Some(clean);
    }

    pub(crate) fn pending_clean(&mut self, snapshots: &Arc<Snapshots>) -> Arc<PendingClean> {
        if let Some(pc) = &mut self.pending_clean {
            pc.clone()
        } else {
            let pc = Arc::new(PendingClean::new(
                snapshots,
                self.journal_id.take(),
                self.freed_pages.take(),
            ));
            self.pending_clean = Some(pc.clone());
            pc
        }
    }

    pub fn id(&self) -> SnapshotId {
        self.snapshot_id
    }
    pub(crate) fn segments_list(&self) -> Option<Vec<(String, SegmentId)>> {
        self.segments.as_ref().map(|segs| {
            segs.values()
                .map(|data| (data.name().to_owned(), data.segment_id()))
                .collect::<Vec<_>>()
        })
    }
    pub(crate) fn find_segment(&self, segment: &str) -> Option<SegmentId> {
        self.segments_name
            .as_ref()
            .and_then(|segs| segs.get(segment).map(|sd| sd.segment_id()))
    }
    pub(crate) fn find_segment_snapsthot(&self, segment_id: SegmentId) -> Option<&SegmentSnapshot> {
        self.segments.as_ref().and_then(|segs| segs.get(&segment_id))
    }
    pub(crate) fn release(&mut self) -> bool {
        self.reference_count -= 1;
        self.reference_count == 0
    }
    pub(crate) fn acquire(&mut self) {
        self.reference_count += 1;
    }
    pub(crate) fn take_entries(&mut self) -> Option<Vec<SnapshotEntry>> {
        self.entries.take()
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RecordVersion {
    snapshot_id: SnapshotId,
    case: EntryCase,
}

impl RecordVersion {
    pub(crate) fn new(snapshot_id: SnapshotId, case: EntryCase) -> Self {
        Self { snapshot_id, case }
    }
    pub(crate) fn id(&self) -> SnapshotId {
        self.snapshot_id
    }
    pub(crate) fn case(&self) -> &EntryCase {
        &self.case
    }
}
