use crate::{Cell, DbHandle};

mod btreemapped;
mod hashmapped;
mod macros;
mod singleton;

pub use btreemapped::BTreeMapStorage;
pub use hashmapped::HashMapStorage;
pub use singleton::SingletonStorage;

/// The Storage trait is implemented on a type which can cache all of the computations
/// used in the program (or a subset of it). These types are typically composed of
/// several fields with each field implementing `StorageFor<T>` where `T` is one
/// computation type. Note that each computation type must be unique within a `Storage` type.
///
/// This trait is most often automatically implemented by `impl_storage!`, see the documentation
/// on that macro for usage details.
///
/// Note that during serialization, the entire Storage is serialized along with the `Db` object.
/// To achieve backwards-compatible serialization even when new fields for new computation types
/// are added, it is recommended to use `#[serde(default)]` on any newly-added fields to still
/// be able to deserialize from older versions without that field.
pub trait Storage: Sized {
    /// For the computation type with the given computation id, return true if the
    /// output with the given Cell has not yet been set.
    fn output_is_unset(&self, cell: Cell, computation_id: u32) -> bool;

    /// For the computation type with the given computation id, run the computation
    /// with the corresponding Cell, returning true if the result changed from its previous value.
    fn run_computation(db: &mut DbHandle<Self>, cell: Cell, computation_id: u32) -> bool;
}

/// This trait is implemented by a type storing a single computation type `C`.
/// Examples include `HashMapStorage<C>`, `SingletonStorage<C>`, and `BTreeMapStorage<C>`.
///
/// To implement this efficiently, most types implementing this are two-way maps
/// from `C` to `Cell` and from `Cell` to `(C, Option<C::Output>)`.
pub trait StorageFor<C: OutputType> {
    /// Given a computation key, return the cell associated with it, if it exists.
    fn get_cell_for_computation(&self, key: &C) -> Option<Cell>;

    /// Insert a new Cell with the given computation that has yet to be run
    fn insert_new_cell(&mut self, cell: Cell, key: C);

    /// Retrieve the input for this computation.
    /// The input is expected to already be inserted into this storage.
    fn get_input(&self, cell: Cell) -> &C;

    /// Retrieve the output for the given cell, if it exists
    fn get_output(&self, cell: Cell) -> Option<&C::Output>;

    /// `C` has been re-run and has returned the output `new_value`, return `true`
    /// if `new_value` has changed from its previous value, and cache the new value
    /// if needed.
    fn update_output(&mut self, cell: Cell, new_value: C::Output) -> bool;
}

pub trait ComputationId {
    fn computation_id() -> u32;
}

pub trait OutputType {
    type Output;
}

pub trait Run<Storage>: OutputType {
    fn run(&self, db: &mut DbHandle<Storage>) -> Self::Output;
}
