//! A transactional filesystem cache layer based on [`freqfs`].
//! See the "examples" directory for usage examples.

use std::{fmt, io};

pub use dir::{Dir, DirEntry, Key, VERSIONS};
pub use file::{File, FileVersionRead, FileVersionWrite};
pub use hr_id::Id;

mod dir;
mod file;

/// The type of error encountered during a transactional filesystem operation
pub enum ErrorKind {
    NotFound,
    Conflict,
    IO,
}

impl fmt::Debug for ErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(match self {
            Self::NotFound => "not found",
            Self::Conflict => "conflict",
            Self::IO => "file IO error",
        })
    }
}

/// An error encountered during a transactional filesystem operation
pub struct Error {
    kind: ErrorKind,
    message: String,
}

impl Error {
    /// Construct an [`Error`].
    pub fn new<I: fmt::Display>(kind: ErrorKind, message: I) -> Self {
        Self {
            kind,
            message: message.to_string(),
        }
    }

    /// Destructure this error information.
    pub fn into_inner(self) -> (ErrorKind, String) {
        (self.kind, self.message)
    }
}

impl From<hr_id::ParseError> for Error {
    fn from(cause: hr_id::ParseError) -> Self {
        Self {
            kind: ErrorKind::IO,
            message: cause.to_string(),
        }
    }
}

impl From<io::Error> for Error {
    fn from(cause: io::Error) -> Self {
        let kind = match cause.kind() {
            io::ErrorKind::NotFound => ErrorKind::NotFound,
            io::ErrorKind::WouldBlock => ErrorKind::Conflict,
            _ => ErrorKind::IO,
        };

        Self {
            kind,
            message: cause.to_string(),
        }
    }
}

impl From<txn_lock::Error> for Error {
    fn from(cause: txn_lock::Error) -> Self {
        Self {
            kind: ErrorKind::Conflict,
            message: cause.to_string(),
        }
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}: {}", self.kind, self.message)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}: {}", self.kind, self.message)
    }
}

impl std::error::Error for Error {}

/// The result of a transactional filesystem operation
pub type Result<T> = std::result::Result<T, Error>;
