use std::borrow::Cow;

use crate::core::{PageCount, PageIdx, VolumeId, page::Page};
use culprit::Culprit;

use crate::{GraftErr, rt::runtime::Runtime, snapshot::Snapshot, volume_writer::VolumeWriter};

/// A type which can read from a Volume
pub trait VolumeRead {
    fn snapshot(&self) -> &Snapshot;
    fn page_count(&self) -> culprit::Result<PageCount, GraftErr>;
    fn read_page(&self, pageidx: PageIdx) -> culprit::Result<Page, GraftErr>;
}

#[derive(Debug, Clone)]
pub struct VolumeReader {
    runtime: Runtime,
    vid: VolumeId,
    snapshot: Snapshot,
}

impl VolumeReader {
    pub(crate) fn new(runtime: Runtime, vid: VolumeId, snapshot: Snapshot) -> Self {
        Self { runtime, vid, snapshot }
    }
}

impl TryFrom<VolumeReader> for VolumeWriter {
    type Error = Culprit<GraftErr>;

    fn try_from(reader: VolumeReader) -> Result<Self, Self::Error> {
        let page_count = reader.page_count()?;
        Ok(Self::new(
            reader.runtime,
            reader.vid,
            reader.snapshot,
            page_count,
        ))
    }
}

impl VolumeRead for VolumeReader {
    fn snapshot(&self) -> &Snapshot {
        &self.snapshot
    }

    fn page_count(&self) -> culprit::Result<PageCount, GraftErr> {
        self.runtime.snapshot_pages(&self.snapshot)
    }

    fn read_page(&self, pageidx: PageIdx) -> culprit::Result<Page, GraftErr> {
        self.runtime.read_page(&self.snapshot, pageidx)
    }
}

pub enum VolumeReadRef<'a> {
    Reader(Cow<'a, VolumeReader>),
    Writer(&'a VolumeWriter),
}

impl VolumeRead for VolumeReadRef<'_> {
    fn snapshot(&self) -> &Snapshot {
        match self {
            VolumeReadRef::Reader(r) => r.snapshot(),
            VolumeReadRef::Writer(w) => w.snapshot(),
        }
    }

    fn page_count(&self) -> culprit::Result<PageCount, GraftErr> {
        match self {
            VolumeReadRef::Reader(r) => r.page_count(),
            VolumeReadRef::Writer(w) => w.page_count(),
        }
    }

    fn read_page(&self, pageidx: PageIdx) -> culprit::Result<Page, GraftErr> {
        match self {
            VolumeReadRef::Reader(r) => r.read_page(pageidx),
            VolumeReadRef::Writer(w) => w.read_page(pageidx),
        }
    }
}
