// Copyright 2018 Zach Miller
//
// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT License (https://opensource.org/licenses/MIT), at your option.
//
// This file may not be copied, modified, or distributed except according to those terms.

mod file_storage;
mod memory_storage;

pub use self::file_storage::FileStorage;
pub use self::memory_storage::MemoryStorage;

use serde::de::DeserializeOwned;
use serde::ser::Serialize;

use error::Result;

/// The `CommittedCheckpoint` trait allows checkpoint data to be retrieved from the storage backing
/// a checkpoint object.
pub trait CommittedCheckpoint {
    /// Retrieves the value associated with the provided key from a checkpoint.
    fn get<T: DeserializeOwned + Serialize>(&mut self, key: &str) -> Result<T>;

    /// Returns a list of keys that are associated with a value in a checkpoint.
    fn keys(&mut self) -> Result<Vec<String>>;

    /// Returns a checkpoint's identifier.
    fn identifier(&mut self) -> Result<&str>;
}

/// The `UncommittedCheckpoint` trait allows checkpoint data to be modified and retrieved from the
/// storage backing a checkpoint object.
pub trait UncommittedCheckpoint {
    /// Retrieves the value associated with the provided key from a checkpoint.
    fn get<T: DeserializeOwned + Serialize>(&mut self, key: &str) -> Result<T>;

    /// Returns a list of keys that are associated with a value in a checkpoint.
    fn keys(&mut self) -> Result<Vec<String>>;

    /// Returns a checkpoint's identifier.
    fn identifier(&mut self) -> Result<&str>;

    /// Stores the provided value in a checkpoint and associates it with the provided key.
    fn put<T: DeserializeOwned + Serialize>(&mut self, key: &str, value: &T) -> Result<()>;

    /// Removes the value associated with the provided key from a checkpoint.
    fn remove(&mut self, key: &str) -> Result<()>;
}

/// The `Storage` trait allows checkpoints to be created, committed, loaded, and removed from the
/// underlying storage medium.
pub trait Storage {
    /// The type representing committed checkpoints.
    type Committed: CommittedCheckpoint;

    /// The type representing uncommitted checkpoints.
    type Uncommitted: UncommittedCheckpoint;

    /// Creates a new checkpoint with the specified identifier.
    ///
    /// The checkpoint will not be saved permanently until it has been committed via the
    /// `commit_checkpoint` method.
    ///
    /// Care must be taken to ensure that the same identifier isn't used for multiple uncommitted
    /// checkpoints. It is recommended that types which implement `Storage` be wrapped in the
    /// [`GuardWrapper`](wrappers/struct.GuardWrapper.html) type (or implement its functionality)
    /// to prevent uncommitted checkpoints from being given the same identifier.
    fn create_checkpoint(&mut self, identifier: &str) -> Result<Self::Uncommitted>;

    /// Commits an uncommitted checkpoint, permanently saving its data to the underlying storage
    /// medium.
    fn commit_checkpoint(&mut self, uncommitted: Self::Uncommitted) -> Result<Self::Committed>;

    /// Loads the committed checkpoint associated with the provided identifier, allowing its data
    /// to be retrieved.
    fn load_checkpoint(&mut self, identifier: &str) -> Result<Self::Committed>;

    /// Removes the committed checkpoint associated with the provided identifier.
    ///
    /// Care must be taken to ensure that checkpoints which are in use (i.e. have a binding created
    /// by either the `commit_checkpoint` method or the `load_checkpoint` method) are not removed.
    /// It is recommended that types which implement `Storage` be wrapped in the
    /// [`GuardWrapper`](wrappers/struct.GuardWrapper.html) type (or implement its functionality)
    /// to prevent accidental removal of checkpoints which are in use.
    fn remove_checkpoint(&mut self, identifier: &str) -> Result<()>;

    /// Returns a list of all committed checkpoint identifiers in the underlying storage medium.
    fn checkpoint_identifiers(&mut self) -> Result<Vec<String>>;
}
