/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::any::Any;
use std::future::Future;
use std::ops::Deref;
use std::sync::Arc;

use allocative::Allocative;
use derivative::Derivative;
use dupe::Dupe;
use futures::FutureExt;
use parking_lot::Mutex;
use parking_lot::MutexGuard;

use crate::api::activation_tracker::ActivationData;
use crate::api::computations::DiceComputations;
use crate::api::data::DiceData;
use crate::api::error::DiceResult;
use crate::api::key::Key;
use crate::api::projection::ProjectionKey;
use crate::api::user_data::UserComputationData;
use crate::ctx::DiceComputationsImpl;
use crate::impls::cache::DiceTaskRef;
use crate::impls::cache::SharedCache;
use crate::impls::core::state::CoreStateHandle;
use crate::impls::core::versions::VersionEpoch;
use crate::impls::dep_trackers::RecordingDepsTracker;
use crate::impls::dice::DiceModern;
use crate::impls::evaluator::AsyncEvaluator;
use crate::impls::evaluator::SyncEvaluator;
use crate::impls::events::DiceEventDispatcher;
use crate::impls::incremental::IncrementalEngine;
use crate::impls::key::CowDiceKeyHashed;
use crate::impls::key::DiceKey;
use crate::impls::key::ParentKey;
use crate::impls::opaque::OpaqueValueModern;
use crate::impls::task::dice::MaybeCancelled;
use crate::impls::task::promise::DicePromise;
use crate::impls::task::sync_dice_task;
use crate::impls::task::PreviouslyCancelledTask;
use crate::impls::transaction::ActiveTransactionGuard;
use crate::impls::transaction::TransactionUpdater;
use crate::impls::user_cycle::KeyComputingUserCycleDetectorData;
use crate::impls::user_cycle::UserCycleDetectorData;
use crate::impls::value::DiceComputedValue;
use crate::impls::value::DiceValidity;
use crate::impls::value::MaybeValidDiceValue;
use crate::result::CancellableResult;
use crate::result::Cancelled;
use crate::versions::VersionNumber;
use crate::DiceError;
use crate::DiceTransactionUpdater;
use crate::HashSet;
use crate::UserCycleDetectorGuard;

/// Context that is the base for which all requests start from
#[derive(Allocative)]
pub(crate) struct BaseComputeCtx {
    // we need to give off references of `DiceComputation` so hold this for now, but really once we
    // get rid of the enum, we just hold onto the base data directly and do some ref casts
    data: DiceComputations,
    live_version_guard: ActiveTransactionGuard,
}

impl Clone for BaseComputeCtx {
    fn clone(&self) -> Self {
        Self {
            data: match &self.data.0 {
                DiceComputationsImpl::Legacy(_) => {
                    unreachable!("wrong dice")
                }
                DiceComputationsImpl::Modern(ctx) => {
                    DiceComputations(DiceComputationsImpl::Modern(PerComputeCtx::new(
                        ParentKey::None,
                        ctx.async_evaluator.per_live_version_ctx.dupe(),
                        ctx.async_evaluator.user_data.dupe(),
                        ctx.async_evaluator.dice.dupe(),
                        KeyComputingUserCycleDetectorData::Untracked,
                    )))
                }
            },
            live_version_guard: self.live_version_guard.dupe(),
        }
    }
}

impl Dupe for BaseComputeCtx {}

impl BaseComputeCtx {
    pub(crate) fn new(
        per_live_version_ctx: SharedLiveTransactionCtx,
        user_data: Arc<UserComputationData>,
        dice: Arc<DiceModern>,
        live_version_guard: ActiveTransactionGuard,
    ) -> Self {
        Self {
            data: DiceComputations(DiceComputationsImpl::Modern(PerComputeCtx::new(
                ParentKey::None,
                per_live_version_ctx,
                user_data,
                dice,
                KeyComputingUserCycleDetectorData::Untracked,
            ))),
            live_version_guard,
        }
    }

    pub(crate) fn get_version(&self) -> VersionNumber {
        self.data.0.get_version()
    }

    pub(crate) fn into_updater(self) -> DiceTransactionUpdater {
        self.data.0.into_updater()
    }

    pub(crate) fn as_computations(&self) -> &DiceComputations {
        &self.data
    }

    pub(crate) fn as_computations_mut(&mut self) -> &mut DiceComputations {
        &mut self.data
    }
}

impl Deref for BaseComputeCtx {
    type Target = PerComputeCtx;

    fn deref(&self) -> &Self::Target {
        match &self.data.0 {
            DiceComputationsImpl::Legacy(_) => {
                unreachable!("legacy dice instead of modern")
            }
            DiceComputationsImpl::Modern(ctx) => ctx,
        }
    }
}

/// Context given to the `compute` function of a `Key`.
#[derive(Allocative)]
pub(crate) struct PerComputeCtx {
    async_evaluator: AsyncEvaluator,
    dep_trackers: Mutex<RecordingDepsTracker>, // If we make PerComputeCtx &mut, we can get rid of this mutex after some refactoring
    parent_key: ParentKey,
    #[allocative(skip)]
    cycles: KeyComputingUserCycleDetectorData,
    // Same as above, PerComputeCtx isn't actually geting shared.
    #[allocative(skip)]
    evaluation_data: Mutex<EvaluationData>,
}

impl PerComputeCtx {
    pub(crate) fn new(
        parent_key: ParentKey,
        per_live_version_ctx: SharedLiveTransactionCtx,
        user_data: Arc<UserComputationData>,
        dice: Arc<DiceModern>,
        cycles: KeyComputingUserCycleDetectorData,
    ) -> Self {
        Self {
            async_evaluator: AsyncEvaluator {
                per_live_version_ctx,
                user_data,
                dice,
            },
            dep_trackers: Mutex::new(RecordingDepsTracker::new()),
            parent_key,
            cycles,
            evaluation_data: Mutex::new(EvaluationData::none()),
        }
    }

    /// Gets all the result of of the given computation key.
    /// recorded as dependencies of the current computation for which this
    /// context is for.
    pub(crate) fn compute<'a, K>(
        &'a self,
        key: &K,
    ) -> impl Future<Output = DiceResult<<K as Key>::Value>> + 'a
    where
        K: Key,
    {
        self.compute_opaque(key)
            .map(|r| r.map(|opaque| opaque.into_value()))
    }

    /// Compute "opaque" value where the value is only accessible via projections.
    /// Projections allow accessing derived results from the "opaque" value,
    /// where the dependency of reading a projection is the projection value rather
    /// than the entire opaque value.
    pub(crate) fn compute_opaque<'a, K>(
        &'a self,
        key: &K,
    ) -> impl Future<Output = DiceResult<OpaqueValueModern<K>>> + 'a
    where
        K: Key,
    {
        let dice_key = self
            .async_evaluator
            .dice
            .key_index
            .index(CowDiceKeyHashed::key_ref(key));

        self.async_evaluator
            .per_live_version_ctx
            .compute_opaque(
                dice_key,
                self.parent_key,
                &self.async_evaluator,
                self.cycles
                    .subrequest(dice_key, &self.async_evaluator.dice.key_index),
            )
            .map(move |cancellable_result| {
                let cancellable = cancellable_result.map(move |dice_value| {
                    OpaqueValueModern::new(self, dice_key, dice_value.value().dupe())
                });

                cancellable.map_err(|_| DiceError::cancelled())
            })
    }

    /// Compute "projection" based on deriving value
    pub(crate) fn project<K>(
        &self,
        key: &K,
        base_key: DiceKey,
        base: MaybeValidDiceValue,
    ) -> DiceResult<K::Value>
    where
        K: ProjectionKey,
    {
        let dice_key = self
            .async_evaluator
            .dice
            .key_index
            .index(CowDiceKeyHashed::proj_ref(base_key, key));

        let r = self
            .async_evaluator
            .per_live_version_ctx
            .compute_projection(
                dice_key,
                self.parent_key,
                self.async_evaluator.dice.state_handle.dupe(),
                SyncEvaluator::new(
                    self.async_evaluator.user_data.dupe(),
                    self.async_evaluator.dice.dupe(),
                    base,
                ),
                DiceEventDispatcher::new(
                    self.async_evaluator.user_data.tracker.dupe(),
                    self.async_evaluator.dice.dupe(),
                ),
            );

        let r = match r {
            Ok(r) => r,
            Err(_cancelled) => return Err(DiceError::cancelled()),
        };

        self.dep_trackers
            .lock()
            .record(dice_key, r.value().validity());

        Ok(r.value()
            .downcast_maybe_transient::<K::Value>()
            .expect("Type mismatch when computing key")
            .dupe())
    }

    /// Data that is static per the entire lifetime of Dice. These data are initialized at the
    /// time that Dice is initialized via the constructor.
    pub(crate) fn global_data(&self) -> &DiceData {
        &self.async_evaluator.dice.global_data
    }

    /// Data that is static for the lifetime of the current request context. This lifetime is
    /// the lifetime of the top-level `DiceComputation` used for all requests.
    /// The data is also specific to each request context, so multiple concurrent requests can
    /// each have their own individual data.
    pub(crate) fn per_transaction_data(&self) -> &UserComputationData {
        &self.async_evaluator.user_data
    }

    pub(crate) fn get_version(&self) -> VersionNumber {
        self.async_evaluator.per_live_version_ctx.get_version()
    }

    pub(crate) fn into_updater(self) -> TransactionUpdater {
        TransactionUpdater::new(
            self.async_evaluator.dice.dupe(),
            self.async_evaluator.user_data.dupe(),
        )
    }

    pub(super) fn dep_trackers(&self) -> MutexGuard<'_, RecordingDepsTracker> {
        self.dep_trackers.lock()
    }

    pub(crate) fn store_evaluation_data<T: Send + Sync + 'static>(
        &self,
        value: T,
    ) -> DiceResult<()> {
        let mut evaluation_data = self.evaluation_data.lock();
        if evaluation_data.0.is_some() {
            return Err(DiceError::duplicate_activation_data());
        }
        evaluation_data.0 = Some(Box::new(value) as _);
        Ok(())
    }

    pub(crate) fn finalize(
        self,
    ) -> (
        (HashSet<DiceKey>, DiceValidity),
        EvaluationData,
        KeyComputingUserCycleDetectorData,
    ) {
        (
            self.dep_trackers.into_inner().collect_deps(),
            self.evaluation_data.into_inner(),
            self.cycles,
        )
    }

    pub(crate) fn cycle_guard<T: UserCycleDetectorGuard>(&self) -> DiceResult<Option<&T>> {
        self.cycles.cycle_guard()
    }
}

/// Context that is shared for all current live computations of the same version.
#[derive(Allocative, Derivative, Dupe, Clone)]
#[derivative(Debug)]
pub(crate) struct SharedLiveTransactionCtx {
    version: VersionNumber,
    version_epoch: VersionEpoch,
    #[derivative(Debug = "ignore")]
    cache: SharedCache,
}

#[allow(clippy::manual_async_fn, unused)]
impl SharedLiveTransactionCtx {
    pub(crate) fn new(v: VersionNumber, version_epoch: VersionEpoch, cache: SharedCache) -> Self {
        Self {
            version: v,
            version_epoch,
            cache,
        }
    }

    /// Compute "opaque" value where the value is only accessible via projections.
    /// Projections allow accessing derived results from the "opaque" value,
    /// where the dependency of reading a projection is the projection value rather
    /// than the entire opaque value.
    pub(crate) fn compute_opaque(
        &self,
        key: DiceKey,
        parent_key: ParentKey,
        eval: &AsyncEvaluator,
        cycles: UserCycleDetectorData,
    ) -> impl Future<Output = CancellableResult<DiceComputedValue>> {
        match self.cache.get(key) {
            DiceTaskRef::Computed(result) => {
                DicePromise::ready(result).left_future()
            }
            DiceTaskRef::Occupied(mut occupied) => {
                match occupied.get().depended_on_by(parent_key) {
                    MaybeCancelled::Ok(promise) => {
                        debug!(msg = "shared state is waiting on existing task", k = ?key, v = ?self.version, v_epoch = ?self.version_epoch);

                        promise
                    },
                    MaybeCancelled::Cancelled => {
                        debug!(msg = "shared state has a cancelled task, spawning new one", k = ?key, v = ?self.version, v_epoch = ?self.version_epoch);

                        let eval = eval.dupe();
                        let events = DiceEventDispatcher::new(
                            eval.user_data.tracker.dupe(),
                            eval.dice.dupe(),
                        );

                        take_mut::take(occupied.get_mut(), |previous| {
                            IncrementalEngine::spawn_for_key(
                                key,
                                self.version_epoch,
                                eval,
                                cycles,
                                events,
                                 Some(PreviouslyCancelledTask {
                                    previous,
                                }),
                            )
                        });

                        occupied
                            .get()
                            .depended_on_by(parent_key)
                            .not_cancelled()
                            .expect("just created")
                    }
                }
                .left_future()
            }
            DiceTaskRef::Vacant(vacant) => {
                debug!(msg = "shared state is empty, spawning new task", k = ?key, v = ?self.version, v_epoch = ?self.version_epoch);

                let eval = eval.dupe();
                let events =
                    DiceEventDispatcher::new(eval.user_data.tracker.dupe(), eval.dice.dupe());

                let task = IncrementalEngine::spawn_for_key(
                    key,
                    self.version_epoch,
                    eval,
                    cycles,
                    events,
                    None,
                );

                let fut = task
                    .depended_on_by(parent_key)
                    .not_cancelled()
                    .expect("just created");

                vacant.insert(task);

                fut.left_future()
            }
            DiceTaskRef::TransactionCancelled => {
                let v = self.version;
                let v_epoch = self.version_epoch;
                async move {
                    debug!(msg = "computing shared state is cancelled", k = ?key, v = ?v, v_epoch = ?v_epoch);
                    tokio::task::yield_now().await;

                    Err(Cancelled)
                }
                    .right_future()
            },
        }
    }

    /// Compute "projection" based on deriving value
    pub(crate) fn compute_projection(
        &self,
        key: DiceKey,
        parent_key: ParentKey,
        state: CoreStateHandle,
        eval: SyncEvaluator,
        events: DiceEventDispatcher,
    ) -> CancellableResult<DiceComputedValue> {
        let promise = match self.cache.get(key) {
            DiceTaskRef::Computed(value) => DicePromise::ready(value),
            DiceTaskRef::Occupied(mut occupied) => {
                match occupied.get().depended_on_by(parent_key) {
                    MaybeCancelled::Ok(promise) => promise,
                    MaybeCancelled::Cancelled => {
                        let task = unsafe {
                            // SAFETY: task completed below by `IncrementalEngine::project_for_key`
                            sync_dice_task(key)
                        };

                        *occupied.get_mut() = task;

                        occupied
                            .get()
                            .depended_on_by(parent_key)
                            .not_cancelled()
                            .expect("just created")
                    }
                }
            }
            DiceTaskRef::Vacant(vacant) => {
                let task = unsafe {
                    // SAFETY: task completed below by `IncrementalEngine::project_for_key`
                    sync_dice_task(key)
                };

                vacant
                    .insert(task)
                    .value()
                    .depended_on_by(parent_key)
                    .not_cancelled()
                    .expect("just created")
            }
            DiceTaskRef::TransactionCancelled => {
                // for projection keys, these are cheap and synchronous computes that should never
                // be cancelled
                let task = unsafe {
                    // SAFETY: task completed below by `IncrementalEngine::project_for_key`
                    sync_dice_task(key)
                };

                task.depended_on_by(parent_key)
                    .not_cancelled()
                    .expect("just created")
            }
        };

        IncrementalEngine::project_for_key(
            state,
            promise,
            key,
            self.version,
            self.version_epoch,
            eval,
            events,
        )
    }

    pub(crate) fn get_version(&self) -> VersionNumber {
        self.version
    }
}

/// Opaque data that the key may have provided during evalution via store_evaluation_data.
pub(crate) struct EvaluationData(Option<Box<dyn Any + Send + Sync + 'static>>);

impl EvaluationData {
    pub(crate) fn none() -> Self {
        Self(None)
    }

    pub(crate) fn into_activation_data(self) -> ActivationData {
        ActivationData::Evaluated(self.0)
    }
}

#[cfg(test)]
pub(crate) mod testing {
    use crate::impls::cache::DiceTaskRef;
    use crate::impls::core::versions::VersionEpoch;
    use crate::impls::ctx::SharedLiveTransactionCtx;
    use crate::impls::key::DiceKey;
    use crate::impls::key::ParentKey;
    use crate::impls::task::sync_dice_task;
    use crate::impls::value::DiceComputedValue;

    impl SharedLiveTransactionCtx {
        pub(crate) fn inject(&self, k: DiceKey, v: DiceComputedValue) {
            let task = unsafe {
                // SAFETY: completed immediately below
                sync_dice_task(k)
            };
            let _r = task
                .depended_on_by(ParentKey::None)
                .not_cancelled()
                .expect("just created")
                .get_or_complete(|| v);

            match self.cache.get(k) {
                DiceTaskRef::Computed(_) => panic!("cannot inject already computed task"),
                DiceTaskRef::Occupied(o) => {
                    o.replace_entry(task);
                }
                DiceTaskRef::Vacant(v) => {
                    v.insert(task);
                }
                DiceTaskRef::TransactionCancelled => panic!("transaction cancelled"),
            }
        }

        pub(crate) fn testing_get_epoch(&self) -> VersionEpoch {
            self.version_epoch
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! Trackers that records dependencies and reverse dependencies during execution of requested nodes

use allocative::Allocative;

use crate::impls::key::DiceKey;
use crate::impls::value::DiceValidity;
use crate::HashSet;

/// The 'DepsTracker' is used to record dependencies of a particular compute node by calling
/// 'record' for each dependency, and then getting a list of 'Dependency's at the end by calling
/// 'collect_deps'.
#[derive(Allocative)]
pub(crate) struct RecordingDepsTracker {
    deps: HashSet<DiceKey>,
    deps_validity: DiceValidity,
}

impl RecordingDepsTracker {
    pub(crate) fn new() -> Self {
        Self {
            deps: HashSet::default(),
            deps_validity: DiceValidity::Valid,
        }
    }

    pub(crate) fn record(&mut self, k: DiceKey, validity: DiceValidity) {
        self.deps.insert(k);
        match validity {
            DiceValidity::Transient => self.deps_validity = validity,
            _ => {}
        }
    }

    pub(crate) fn collect_deps(self) -> (HashSet<DiceKey>, DiceValidity) {
        (self.deps, self.deps_validity)
    }
}

#[cfg(test)]
pub(crate) mod testing {

    use crate::impls::dep_trackers::RecordingDepsTracker;
    use crate::impls::key::DiceKey;
    use crate::HashSet;

    pub(crate) trait RecordingDepsTrackersExt {
        fn recorded_deps(&self) -> &HashSet<DiceKey>;
    }

    impl RecordingDepsTrackersExt for RecordingDepsTracker {
        fn recorded_deps(&self) -> &HashSet<DiceKey> {
            &self.deps
        }
    }
}

#[cfg(test)]
mod tests {

    use crate::impls::dep_trackers::RecordingDepsTracker;
    use crate::impls::key::DiceKey;
    use crate::impls::value::DiceValidity;
    use crate::HashSet;

    #[tokio::test]
    async fn recording_deps_tracker_tracks_deps() -> anyhow::Result<()> {
        let mut deps_tracker = RecordingDepsTracker::new();

        deps_tracker.record(DiceKey { index: 2 }, DiceValidity::Valid);
        deps_tracker.record(DiceKey { index: 3 }, DiceValidity::Valid);

        let (deps, validity) = deps_tracker.collect_deps();
        let expected = HashSet::from_iter([DiceKey { index: 2 }, DiceKey { index: 3 }]);
        assert_eq!(deps, expected);
        assert_eq!(validity, DiceValidity::Valid);

        Ok(())
    }

    #[test]
    fn recording_deps_tracker_tracks_deps_invalid() -> anyhow::Result<()> {
        let mut deps_tracker = RecordingDepsTracker::new();

        deps_tracker.record(DiceKey { index: 2 }, DiceValidity::Valid);
        deps_tracker.record(DiceKey { index: 3 }, DiceValidity::Transient);

        let (deps, validity) = deps_tracker.collect_deps();
        let expected = HashSet::from_iter([DiceKey { index: 2 }, DiceKey { index: 3 }]);
        assert_eq!(deps, expected);
        assert_eq!(validity, DiceValidity::Transient);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt::Debug;
use std::future::Future;
use std::sync::Arc;

use allocative::Allocative;
use dupe::Dupe;

use crate::api::cycles::DetectCycles;
use crate::api::data::DiceData;
use crate::api::user_data::UserComputationData;
use crate::impls::core::state::init_state;
use crate::impls::core::state::CoreStateHandle;
use crate::impls::core::state::StateRequest;
use crate::impls::key_index::DiceKeyIndex;
use crate::impls::transaction::TransactionUpdater;
use crate::introspection::graph::GraphIntrospectable;
use crate::metrics::Metrics;

#[derive(Allocative)]
pub(crate) struct DiceModern {
    pub(crate) key_index: DiceKeyIndex,
    pub(crate) state_handle: CoreStateHandle,
    pub(crate) global_data: DiceData,
}

impl Debug for DiceModern {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DiceModern").finish_non_exhaustive()
    }
}

pub(crate) struct DiceModernDataBuilder(DiceData);

impl DiceModernDataBuilder {
    pub(crate) fn new() -> Self {
        Self(DiceData::new())
    }

    pub fn set<K: Send + Sync + 'static>(&mut self, val: K) {
        self.0.set(val);
    }

    pub fn build(self, _detect_cycles: DetectCycles) -> Arc<DiceModern> {
        DiceModern::new(self.0)
    }
}

impl DiceModern {
    pub(crate) fn new(global_data: DiceData) -> Arc<Self> {
        let state_handle = init_state();

        Arc::new(DiceModern {
            key_index: Default::default(),
            state_handle,
            global_data,
        })
    }

    #[cfg(test)]
    pub(crate) fn builder() -> DiceModernDataBuilder {
        DiceModernDataBuilder::new()
    }

    pub fn updater(self: &Arc<Self>) -> TransactionUpdater {
        self.updater_with_data(UserComputationData::new())
    }

    pub fn updater_with_data(self: &Arc<Self>, extra: UserComputationData) -> TransactionUpdater {
        TransactionUpdater::new(self.dupe(), Arc::new(extra))
    }

    pub fn metrics(&self) -> Metrics {
        let (tx, rx) = tokio::sync::oneshot::channel();

        self.state_handle
            .request(StateRequest::Metrics { resp: tx });

        // Modern dice can just run on a blocking runtime and block waiting for the channel.
        // This is safe since the processing dice thread is dedicated, and never awaits any other tasks.
        tokio::task::block_in_place(|| rx.blocking_recv().unwrap())
    }

    pub fn to_introspectable(&self) -> GraphIntrospectable {
        let (tx, rx) = tokio::sync::oneshot::channel();

        self.state_handle.request(StateRequest::Introspection {
            resp: tx,
            key_map: self.key_index.introspect(),
        });

        rx.blocking_recv().unwrap()
    }

    /// Note: modern dice does not support cycle detection yet
    pub fn detect_cycles(&self) -> &DetectCycles {
        // TODO(bobyf) actually have cycles for dice modern
        const CYCLES: DetectCycles = DetectCycles::Disabled;
        &CYCLES
    }

    /// Wait until all active versions have exited.
    pub fn wait_for_idle(&self) -> impl Future<Output = ()> + 'static {
        let (tx, rx) = tokio::sync::oneshot::channel();

        self.state_handle
            .request(StateRequest::GetTasksPendingCancellation { resp: tx });

        async move {
            let tasks = rx.await.unwrap();
            futures::future::join_all(tasks).await;
        }
    }

    /// true when there are no tasks pending cancellation
    pub async fn is_idle(&self) -> bool {
        let (tx, rx) = tokio::sync::oneshot::channel();

        self.state_handle
            .request(StateRequest::GetTasksPendingCancellation { resp: tx });

        let tasks = rx.await.unwrap();

        tasks.iter().all(|task| task.is_terminated())
    }
}

#[cfg(test)]
pub(crate) mod testing {
    use dupe::Dupe;

    use crate::impls::core::state::StateRequest;
    use crate::impls::ctx::SharedLiveTransactionCtx;
    use crate::impls::dice::DiceModern;
    use crate::impls::transaction::ActiveTransactionGuard;
    use crate::versions::VersionNumber;

    impl DiceModern {
        pub(crate) async fn testing_shared_ctx(
            &self,
            v: VersionNumber,
        ) -> (SharedLiveTransactionCtx, ActiveTransactionGuard) {
            let (tx, rx) = tokio::sync::oneshot::channel();

            let guard = ActiveTransactionGuard::new(v, self.state_handle.dupe());
            self.state_handle.request(StateRequest::CtxAtVersion {
                version: v,
                guard,
                resp: tx,
            });
            rx.await.unwrap()
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use allocative::Allocative;
use dupe::Dupe;

use crate::api::computations::DiceComputations;
use crate::api::projection::DiceProjectionComputations;
use crate::api::storage_type::StorageType;
use crate::api::user_data::UserComputationData;
use crate::ctx::DiceComputationsImpl;
use crate::impls::ctx::EvaluationData;
use crate::impls::ctx::PerComputeCtx;
use crate::impls::ctx::SharedLiveTransactionCtx;
use crate::impls::dice::DiceModern;
use crate::impls::key::DiceKey;
use crate::impls::key::DiceKeyErased;
use crate::impls::key::ParentKey;
use crate::impls::value::MaybeValidDiceValue;
use crate::impls::worker::state::DiceWorkerStateComputing;
use crate::impls::worker::state::DiceWorkerStateFinishedEvaluating;
use crate::result::CancellableResult;
use crate::HashSet;

/// Evaluates Keys
#[derive(Clone, Dupe, Allocative)]
pub(crate) struct AsyncEvaluator {
    pub(super) per_live_version_ctx: SharedLiveTransactionCtx,
    pub(super) user_data: Arc<UserComputationData>,
    pub(super) dice: Arc<DiceModern>,
}

impl AsyncEvaluator {
    pub(crate) fn storage_type(&self, key: DiceKey) -> StorageType {
        let key_erased = self.dice.key_index.get(key);
        match key_erased {
            DiceKeyErased::Key(k) => k.storage_type(),
            DiceKeyErased::Projection(p) => p.proj().storage_type(),
        }
    }

    pub(crate) async fn evaluate<'a>(
        &'a self,
        key: DiceKey,
        state: DiceWorkerStateComputing<'a>,
    ) -> CancellableResult<DiceWorkerStateFinishedEvaluating> {
        let key_erased = self.dice.key_index.get(key);

        let (cycles, state) = state.evaluating();

        match key_erased {
            DiceKeyErased::Key(key_dyn) => {
                let new_ctx = DiceComputations(DiceComputationsImpl::Modern(PerComputeCtx::new(
                    ParentKey::Some(key), // within this key's compute, this key is the parent
                    self.per_live_version_ctx.dupe(),
                    self.user_data.dupe(),
                    self.dice.dupe(),
                    cycles,
                )));

                let value = key_dyn
                    .compute(&new_ctx, &state.cancellation_ctx().into_compatible())
                    .await;
                let ((deps, dep_validity), evaluation_data, cycles) = match new_ctx.0 {
                    DiceComputationsImpl::Legacy(_) => {
                        unreachable!("modern dice created above")
                    }
                    DiceComputationsImpl::Modern(new_ctx) => new_ctx.finalize(),
                };

                state.finished(
                    cycles,
                    KeyEvaluationResult {
                        value: MaybeValidDiceValue::new(value, dep_validity),
                        deps,
                        storage: key_dyn.storage_type(),
                        evaluation_data,
                    },
                )
            }
            DiceKeyErased::Projection(proj) => {
                let base = self
                    .per_live_version_ctx
                    .compute_opaque(
                        proj.base(),
                        ParentKey::Some(key), // the parent requesting the projection base is the projection itself
                        self,
                        cycles.subrequest(proj.base(), &self.dice.key_index),
                    )
                    .await?;

                let ctx = DiceProjectionComputations {
                    data: &self.dice.global_data,
                    user_data: &self.user_data,
                };

                let value = proj.proj().compute(base.value(), &ctx);

                state.finished(
                    cycles,
                    KeyEvaluationResult {
                        value: MaybeValidDiceValue::new(value, base.value().validity()),
                        deps: [proj.base()].into_iter().collect(),
                        storage: proj.proj().storage_type(),
                        evaluation_data: EvaluationData::none(), // Projection keys can't set this.
                    },
                )
            }
        }
    }
}

/// Evaluates Keys
#[derive(Clone, Dupe)]
pub(crate) struct SyncEvaluator {
    user_data: Arc<UserComputationData>,
    dice: Arc<DiceModern>,
    base: MaybeValidDiceValue,
}

impl SyncEvaluator {
    pub(crate) fn new(
        user_data: Arc<UserComputationData>,
        dice: Arc<DiceModern>,
        base: MaybeValidDiceValue,
    ) -> Self {
        Self {
            user_data,
            dice,
            base,
        }
    }

    pub(crate) fn evaluate(&self, key: DiceKey) -> KeyEvaluationResult {
        let key_erased = self.dice.key_index.get(key);
        match key_erased {
            DiceKeyErased::Key(_) => {
                unreachable!("cannot evaluate async keys synchronously")
            }
            DiceKeyErased::Projection(proj) => {
                let ctx = DiceProjectionComputations {
                    data: &self.dice.global_data,
                    user_data: &self.user_data,
                };

                let value = proj.proj().compute(&self.base, &ctx);

                KeyEvaluationResult {
                    value: MaybeValidDiceValue::new(value, self.base.validity()),
                    deps: [proj.base()].into_iter().collect(),
                    storage: proj.proj().storage_type(),
                    evaluation_data: EvaluationData::none(), // Projection keys can't set this.
                }
            }
        }
    }
}

pub(crate) struct KeyEvaluationResult {
    pub(crate) value: MaybeValidDiceValue,
    pub(crate) deps: HashSet<DiceKey>,
    pub(crate) storage: StorageType,
    pub(crate) evaluation_data: EvaluationData,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use dupe::Dupe;

use crate::api::events::DiceEvent;
use crate::api::events::DiceEventListener;
use crate::impls::dice::DiceModern;
use crate::impls::key::DiceKey;

#[derive(Clone, Dupe)]
pub(crate) struct DiceEventDispatcher {
    tracker: Arc<dyn DiceEventListener>,
    dice: Arc<DiceModern>,
}

impl DiceEventDispatcher {
    pub(crate) fn new(tracker: Arc<dyn DiceEventListener>, dice: Arc<DiceModern>) -> Self {
        Self { tracker, dice }
    }

    pub(crate) fn started(&self, k: DiceKey) {
        let desc = self.dice.key_index.get(k).key_type_name();

        self.tracker.event(DiceEvent::Started { key_type: desc })
    }

    pub(crate) fn finished(&self, k: DiceKey) {
        let desc = self.dice.key_index.get(k).key_type_name();

        self.tracker.event(DiceEvent::Finished { key_type: desc })
    }

    pub(crate) fn check_deps_started(&self, k: DiceKey) {
        let desc = self.dice.key_index.get(k).key_type_name();

        self.tracker
            .event(DiceEvent::CheckDepsStarted { key_type: desc })
    }

    pub(crate) fn check_deps_finished(&self, k: DiceKey) {
        let desc = self.dice.key_index.get(k).key_type_name();

        self.tracker
            .event(DiceEvent::CheckDepsFinished { key_type: desc })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::any::TypeId;
use std::hash::Hash;
use std::hash::Hasher;
use std::mem;

use fnv::FnvHasher;

pub(crate) fn key_hash<K: Hash + 'static>(key: &K) -> u64 {
    let mut hasher = FnvHasher::default();
    if mem::size_of::<K>() == 0 {
        // Hashing `TypeId` unconditionally measurably slows down hashing.
        TypeId::of::<K>().hash(&mut hasher);
    } else {
        key.hash(&mut hasher);
    }
    hasher.finish()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::any::Any;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;

use allocative::Allocative;
use async_trait::async_trait;
use cmp_any::PartialEqAny;
use derive_more::Display;
use dupe::Dupe;
use fnv::FnvHasher;
use more_futures::cancellation::CancellationContext;

use crate::api::computations::DiceComputations;
use crate::api::key::Key;
use crate::api::projection::DiceProjectionComputations;
use crate::api::projection::ProjectionKey;
use crate::api::storage_type::StorageType;
use crate::impls::hash;
use crate::impls::hash::key_hash;
use crate::impls::value::DiceKeyValue;
use crate::impls::value::DiceProjectValue;
use crate::impls::value::DiceValueDyn;
use crate::impls::value::MaybeValidDiceValue;

/// Type erased internal dice key
#[derive(
    Allocative, Eq, PartialEq, Clone, Copy, Dupe, Hash, Debug, Ord, PartialOrd
)]
pub(crate) struct DiceKey {
    /// represented as indexes into an internal index based map
    pub(crate) index: u32,
}

/// Key of the parent computation
#[derive(
    Allocative, Eq, PartialEq, Clone, Copy, Dupe, Hash, Debug, Ord, PartialOrd
)]
pub(crate) enum ParentKey {
    None,
    Some(DiceKey),
}

#[derive(Allocative, Clone, Dupe, Display)]
pub(crate) enum DiceKeyErased {
    Key(Arc<dyn DiceKeyDyn>),
    Projection(ProjectionWithBase),
}

impl DiceKeyErased {
    pub(crate) fn key<K: Key>(k: K) -> Self {
        Self::Key(Arc::new(k))
    }

    #[allow(unused)] // counterpart of projection to `key`
    pub(crate) fn proj<K: ProjectionKey>(base: DiceKey, k: K) -> Self {
        Self::Projection(ProjectionWithBase {
            base,
            proj: Arc::new(k),
        })
    }

    pub(crate) fn key_type_name(&self) -> &'static str {
        match self {
            DiceKeyErased::Key(k) => k.key_type_name(),
            DiceKeyErased::Projection(k) => k.proj.key_type_name(),
        }
    }

    pub(crate) fn hash(&self) -> u64 {
        match self {
            DiceKeyErased::Key(k) => k.hash(),
            DiceKeyErased::Projection(proj) => proj.hash(),
        }
    }

    pub(crate) fn as_any(&self) -> &dyn Any {
        match self {
            DiceKeyErased::Key(k) => k.as_any(),
            DiceKeyErased::Projection(proj) => proj.proj().as_any(),
        }
    }

    pub(crate) fn downcast<K: 'static>(self) -> Option<Arc<K>> {
        match self {
            DiceKeyErased::Key(k) => {
                if k.as_any().is::<K>() {
                    Some(unsafe { Arc::from_raw(Arc::into_raw(k).cast()) })
                } else {
                    None
                }
            }
            DiceKeyErased::Projection(proj) => {
                if proj.proj.as_any().is::<K>() {
                    Some(unsafe { Arc::from_raw(Arc::into_raw(proj.proj).cast()) })
                } else {
                    None
                }
            }
        }
    }

    pub(crate) fn as_ref<'a>(&'a self) -> DiceKeyErasedRef<'a> {
        match self {
            DiceKeyErased::Key(k) => DiceKeyErasedRef::Key(&**k),
            DiceKeyErased::Projection(proj) => {
                DiceKeyErasedRef::Projection(ProjectionWithBaseRef {
                    base: proj.base,
                    proj: &*proj.proj,
                })
            }
        }
    }
}

impl PartialEq for DiceKeyErased {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (DiceKeyErased::Key(k), DiceKeyErased::Key(ok)) => k.cmp_any() == ok.cmp_any(),
            (DiceKeyErased::Projection(k), DiceKeyErased::Projection(ok)) => k == ok,
            _ => false,
        }
    }
}

impl Eq for DiceKeyErased {}

#[derive(Copy, Clone, Dupe, Display)]
pub(crate) enum DiceKeyErasedRef<'a> {
    Key(&'a dyn DiceKeyDyn),
    Projection(ProjectionWithBaseRef<'a>),
}

impl<'a> DiceKeyErasedRef<'a> {
    pub(crate) fn key<K: Key>(k: &'a K) -> Self {
        Self::Key(k)
    }

    pub(crate) fn proj<K: ProjectionKey>(base: DiceKey, k: &'a K) -> Self {
        Self::Projection(ProjectionWithBaseRef { base, proj: k })
    }

    pub(crate) fn hash(&self) -> u64 {
        match self {
            DiceKeyErasedRef::Key(k) => k.hash(),
            DiceKeyErasedRef::Projection(p) => p.hash(),
        }
    }

    #[allow(unused)] // generally useful function that is the counterpart on `DiceKeyErased`
    pub(crate) fn key_type_name(&self) -> &'static str {
        match self {
            DiceKeyErasedRef::Key(k) => k.key_type_name(),
            DiceKeyErasedRef::Projection(k) => k.proj.key_type_name(),
        }
    }

    fn to_owned(&self) -> DiceKeyErased {
        match self {
            DiceKeyErasedRef::Key(k) => DiceKeyErased::Key(k.clone_arc()),
            DiceKeyErasedRef::Projection(p) => DiceKeyErased::Projection(p.to_owned()),
        }
    }
}

impl<'a> PartialEq for DiceKeyErasedRef<'a> {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (DiceKeyErasedRef::Key(k), DiceKeyErasedRef::Key(ok)) => k.cmp_any() == ok.cmp_any(),
            (DiceKeyErasedRef::Projection(k), DiceKeyErasedRef::Projection(ok)) => k == ok,
            _ => false,
        }
    }
}

#[derive(Display)]
pub(crate) enum CowDiceKey<'a> {
    Ref(DiceKeyErasedRef<'a>),
    Owned(DiceKeyErased),
}

impl<'a> CowDiceKey<'a> {
    pub(crate) fn into_owned(self) -> DiceKeyErased {
        match self {
            CowDiceKey::Ref(r) => r.to_owned(),
            CowDiceKey::Owned(owned) => owned,
        }
    }

    pub(crate) fn borrow(&'a self) -> DiceKeyErasedRef<'a> {
        match self {
            CowDiceKey::Ref(r) => *r,
            CowDiceKey::Owned(owned) => owned.as_ref(),
        }
    }
}

pub(crate) struct CowDiceKeyHashed<'a> {
    cow: CowDiceKey<'a>,
    hash: u64,
}

impl<'a> CowDiceKeyHashed<'a> {
    pub(crate) fn key_ref<K: Key>(key: &K) -> CowDiceKeyHashed {
        let hash = key_hash(key);
        CowDiceKeyHashed {
            cow: CowDiceKey::Ref(DiceKeyErasedRef::key(key)),
            hash,
        }
    }

    pub(crate) fn proj_ref<K: ProjectionKey>(base: DiceKey, k: &'a K) -> CowDiceKeyHashed {
        let key = DiceKeyErasedRef::proj(base, k);
        let hash = key.hash();
        CowDiceKeyHashed {
            cow: CowDiceKey::Ref(key),
            hash,
        }
    }

    pub(crate) fn key<K: Key>(key: K) -> CowDiceKeyHashed<'static> {
        let hash = key_hash(&key);
        CowDiceKeyHashed {
            cow: CowDiceKey::Owned(DiceKeyErased::key(key)),
            hash,
        }
    }

    pub(crate) fn hash(&self) -> u64 {
        self.hash
    }

    pub(crate) fn into_cow(self) -> CowDiceKey<'a> {
        self.cow
    }
}

impl<'a> Display for CowDiceKeyHashed<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.cow)
    }
}

#[async_trait]
pub(crate) trait DiceKeyDyn: Allocative + Display + Send + Sync + 'static {
    async fn compute(
        &self,
        ctx: &DiceComputations,
        cancellations: &CancellationContext,
    ) -> Arc<dyn DiceValueDyn>;

    fn cmp_any(&self) -> PartialEqAny;

    fn hash(&self) -> u64;

    fn as_any(&self) -> &dyn Any;

    fn clone_arc(&self) -> Arc<dyn DiceKeyDyn>;

    fn key_type_name(&self) -> &'static str;

    fn storage_type(&self) -> StorageType;
}

#[async_trait]
impl<K> DiceKeyDyn for K
where
    K: Key,
{
    async fn compute(
        &self,
        ctx: &DiceComputations,
        cancellations: &CancellationContext,
    ) -> Arc<dyn DiceValueDyn> {
        let value = self.compute(ctx, cancellations).await;
        Arc::new(DiceKeyValue::<K>::new(value))
    }

    fn cmp_any(&self) -> PartialEqAny {
        PartialEqAny::new(self)
    }

    fn hash(&self) -> u64 {
        hash::key_hash(self)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn clone_arc(&self) -> Arc<dyn DiceKeyDyn> {
        Arc::new(self.clone())
    }

    fn key_type_name(&self) -> &'static str {
        K::key_type_name()
    }

    fn storage_type(&self) -> StorageType {
        K::storage_type()
    }
}

pub(crate) trait DiceProjectionDyn: Allocative + Display + Send + Sync + 'static {
    fn compute(
        &self,
        derive_from: &MaybeValidDiceValue,
        ctx: &DiceProjectionComputations,
    ) -> Arc<dyn DiceValueDyn>;

    fn cmp_any(&self) -> PartialEqAny;

    fn hash(&self) -> u64;

    fn as_any(&self) -> &dyn Any;

    fn clone_arc(&self) -> Arc<dyn DiceProjectionDyn>;

    fn key_type_name(&self) -> &'static str;

    fn storage_type(&self) -> StorageType;
}

impl<K> DiceProjectionDyn for K
where
    K: ProjectionKey,
{
    fn compute(
        &self,
        derive_from: &MaybeValidDiceValue,
        ctx: &DiceProjectionComputations,
    ) -> Arc<dyn DiceValueDyn> {
        let value = self.compute(
            derive_from
                .downcast_maybe_transient()
                .expect("Projection Key derived from wrong type"),
            ctx,
        );
        Arc::new(DiceProjectValue::<K>::new(value))
    }

    fn cmp_any(&self) -> PartialEqAny {
        PartialEqAny::new(self)
    }

    fn hash(&self) -> u64 {
        hash::key_hash(self)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn clone_arc(&self) -> Arc<dyn DiceProjectionDyn> {
        Arc::new(self.clone())
    }

    fn key_type_name(&self) -> &'static str {
        K::key_type_name()
    }

    fn storage_type(&self) -> StorageType {
        K::storage_type()
    }
}

#[derive(Allocative, Clone, Dupe)]
pub(crate) struct ProjectionWithBase {
    base: DiceKey,
    proj: Arc<dyn DiceProjectionDyn>,
}

impl ProjectionWithBase {
    pub(crate) fn base(&self) -> DiceKey {
        self.base
    }

    pub(crate) fn proj(&self) -> &dyn DiceProjectionDyn {
        &*self.proj
    }

    fn hash(&self) -> u64 {
        let mut hasher = FnvHasher::default();

        self.base.hash(&mut hasher);
        self.proj.hash().hash(&mut hasher);

        hasher.finish()
    }
}

impl PartialEq for ProjectionWithBase {
    fn eq(&self, other: &Self) -> bool {
        self.proj.cmp_any() == other.proj.cmp_any() && self.base == other.base
    }
}

impl Eq for ProjectionWithBase {}

impl Display for ProjectionWithBase {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.proj)
    }
}

#[derive(Copy, Clone, Dupe)]
pub(crate) struct ProjectionWithBaseRef<'a> {
    base: DiceKey,
    proj: &'a dyn DiceProjectionDyn,
}

impl<'a> ProjectionWithBaseRef<'a> {
    fn to_owned(&self) -> ProjectionWithBase {
        ProjectionWithBase {
            base: self.base,
            proj: self.proj.clone_arc(),
        }
    }

    fn hash(&self) -> u64 {
        let mut hasher = FnvHasher::default();

        self.base.hash(&mut hasher);
        self.proj.hash().hash(&mut hasher);

        hasher.finish()
    }
}

impl<'a> PartialEq for ProjectionWithBaseRef<'a> {
    fn eq(&self, other: &Self) -> bool {
        self.proj.cmp_any() == other.proj.cmp_any() && self.base == other.base
    }
}

impl<'a> Eq for ProjectionWithBaseRef<'a> {}

impl<'a> Display for ProjectionWithBaseRef<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.proj)
    }
}

mod introspection {
    use std::fmt::Display;
    use std::fmt::Formatter;
    use std::hash::Hash;
    use std::hash::Hasher;

    use cmp_any::PartialEqAny;
    use dupe::Dupe;

    use crate::impls::key::DiceKey;
    use crate::impls::key::DiceKeyErased;
    use crate::introspection::graph::AnyKey;
    use crate::introspection::graph::KeyForIntrospection;
    use crate::introspection::graph::KeyID;

    impl DiceKey {
        pub(crate) fn introspect(&self) -> KeyID {
            KeyID(self.index as usize)
        }
    }

    impl DiceKeyErased {
        pub(crate) fn introspect(&self) -> AnyKey {
            #[derive(Clone, Dupe)]
            struct Wrap(DiceKeyErased);

            impl PartialEq for Wrap {
                fn eq(&self, other: &Self) -> bool {
                    self.0 == other.0
                }
            }

            impl Eq for Wrap {}

            impl Display for Wrap {
                fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
                    match &self.0 {
                        DiceKeyErased::Key(k) => {
                            write!(f, "{}", k)
                        }
                        DiceKeyErased::Projection(p) => {
                            write!(f, "{}", p.proj)
                        }
                    }
                }
            }

            impl KeyForIntrospection for Wrap {
                fn get_key_equality(&self) -> PartialEqAny {
                    PartialEqAny::new(self)
                }

                fn hash(&self, mut state: &mut dyn Hasher) {
                    Hash::hash(&self.0.hash(), &mut state);
                }

                fn box_clone(&self) -> Box<dyn KeyForIntrospection> {
                    Box::new(self.clone())
                }

                fn type_name(&self) -> &'static str {
                    match &self.0 {
                        DiceKeyErased::Key(k) => k.key_type_name(),
                        DiceKeyErased::Projection(p) => p.proj.key_type_name(),
                    }
                }
            }

            AnyKey::new(Wrap(self.dupe()))
        }
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use allocative::Allocative;
    use derive_more::Display;
    use dupe::Dupe;
    use more_futures::cancellation::CancellationContext;

    use crate::api::computations::DiceComputations;
    use crate::api::key::Key;
    use crate::api::projection::DiceProjectionComputations;
    use crate::api::projection::ProjectionKey;
    use crate::impls::key::DiceKey;
    use crate::impls::key::DiceKeyErased;

    #[test]
    fn downcast_key_does_not_increase_refs() {
        #[derive(Allocative, Debug, Display, Clone, Dupe, Eq, PartialEq, Hash)]
        struct TestK;

        #[async_trait::async_trait]
        impl Key for TestK {
            type Value = ();

            async fn compute(
                &self,
                _ctx: &DiceComputations,
                _cancellations: &CancellationContext,
            ) -> Self::Value {
                unimplemented!("test")
            }

            fn equality(_x: &Self::Value, _y: &Self::Value) -> bool {
                unimplemented!("test")
            }
        }

        let erased = DiceKeyErased::key(TestK);

        let downcast = erased.downcast::<TestK>();
        assert!(downcast.is_some());
        let downcast = downcast.unwrap();

        assert_eq!(&*downcast, &TestK);

        // no extra copies
        assert_eq!(Arc::strong_count(&downcast), 1);

        #[derive(Allocative, Debug, Display, Clone, Dupe, Eq, PartialEq, Hash)]
        struct TestProj;

        impl ProjectionKey for TestProj {
            type DeriveFromKey = TestK;
            type Value = ();

            fn compute(
                &self,
                _derive_from: &<<Self as ProjectionKey>::DeriveFromKey as Key>::Value,
                _ctx: &DiceProjectionComputations,
            ) -> Self::Value {
                unimplemented!("test")
            }

            fn equality(_x: &Self::Value, _y: &Self::Value) -> bool {
                unimplemented!("test")
            }
        }

        let erased = DiceKeyErased::proj(DiceKey { index: 99 }, TestProj);

        let downcast = erased.downcast::<TestProj>();
        assert!(downcast.is_some());
        let downcast = downcast.unwrap();

        assert_eq!(&*downcast, &TestProj);

        // no extra copies
        assert_eq!(Arc::strong_count(&downcast), 1);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::array;
use std::num::NonZeroU32;

use allocative::Allocative;
use lock_free_hashtable::raw::LockFreeRawTable;
use lock_free_vec::LockFreeVec;
use parking_lot::Mutex;
use parking_lot::MutexGuard;

use crate::impls::key::CowDiceKeyHashed;
use crate::impls::key::DiceKey;
use crate::impls::key::DiceKeyErased;
use crate::impls::key::DiceKeyErasedRef;
use crate::Key;

const KEY_BY_INDEX_BUCKETS: usize =
    lock_free_vec::buckets_for_max_capacity(DiceKeyIndex::MAX_INDEX_IN_SHARD as usize + 1);

/// We bound each shard to only store up to u32 size entry. Together with `SHARDS`s shards, this
/// is capable to ~4 billion keys. After which point, it is probably too large for DICE to
/// store in memory anyways.
#[derive(Allocative, Default)]
struct Shard {
    table: LockFreeRawTable<NonZeroU32>,
    key_by_index: LockFreeVec<DiceKeyErased, KEY_BY_INDEX_BUCKETS>,
    /// Mutex is used for updates. Lookups do not need to acquire the lock.
    mutex: Mutex<()>,
}

impl Shard {
    fn get(&self, key: DiceKeyErasedRef, hash: u64) -> Option<u32> {
        self.table
            .lookup(hash, |k| {
                self.key_by_index
                    .get(k.get() as usize - 1)
                    .unwrap()
                    .as_ref()
                    == key
            })
            .map(|k| k.get() - 1)
    }

    fn insert_unique_unchecked(
        &self,
        _lock: &MutexGuard<()>,
        key: DiceKeyErased,
        hash: u64,
    ) -> u32 {
        assert!(
            self.key_by_index.len() < DiceKeyIndex::MAX_INDEX_IN_SHARD as usize,
            "too many dice keys"
        );
        let index = self.key_by_index.len() as u32;
        let off_by_one = NonZeroU32::new(index + 1).unwrap();
        self.key_by_index.push_at(index as usize, key).ok().unwrap();
        self.table.insert(
            hash,
            off_by_one,
            |k1, k2| {
                // We could use something like `insert_unique_unchecked`,
                // which currently does not exist in `LockFreeRawTable`.
                k1 == k2
            },
            |k| self.key_by_index.get(k.get() as usize - 1).unwrap().hash(),
        );
        index
    }
}

#[derive(Allocative)]
pub(crate) struct DiceKeyIndex {
    shards: [Shard; DiceKeyIndex::SHARDS as usize],
}

impl Default for DiceKeyIndex {
    fn default() -> DiceKeyIndex {
        DiceKeyIndex {
            shards: array::from_fn(|_| Shard::default()),
        }
    }
}

impl DiceKeyIndex {
    pub(crate) const SHARDS: u32 = 64;
    pub(crate) const MAX_INDEX_IN_SHARD: u32 = u32::MAX / DiceKeyIndex::SHARDS;

    #[inline]
    fn shard_index_for_hash(hash: u64) -> u32 {
        // `LockFreeRawTable` uses low bits to select bucket.
        // So we should not use low bits as is to select shard.
        (hash >> 32) as u32 % DiceKeyIndex::SHARDS
    }

    pub(crate) fn index(&self, key: CowDiceKeyHashed) -> DiceKey {
        let hash = key.hash();
        let key = key.into_cow();
        let shard_index = DiceKeyIndex::shard_index_for_hash(hash);
        let shard = &self.shards[shard_index as usize];

        // First try lookup without locking.
        if let Some(index_in_shard) = shard.get(key.borrow(), hash) {
            return DiceKeyUnpacked {
                shard_index,
                index_in_shard,
            }
            .pack();
        }

        // If not found, lock and try insert.
        let guard = shard.mutex.lock();
        let index_in_shard = if let Some(index_in_shard) = shard.get(key.borrow(), hash) {
            index_in_shard
        } else {
            let index_in_shard = shard.insert_unique_unchecked(&guard, key.into_owned(), hash);

            trace!(
                "{} ({}) maps to {:?}",
                shard.key_by_index.get(index_in_shard as usize).unwrap(),
                shard
                    .key_by_index
                    .get(index_in_shard as usize)
                    .unwrap()
                    .key_type_name(),
                DiceKeyUnpacked {
                    shard_index,
                    index_in_shard,
                }
                .pack()
            );

            index_in_shard
        };
        DiceKeyUnpacked {
            shard_index,
            index_in_shard,
        }
        .pack()
    }

    pub(crate) fn index_key<K: Key>(&self, key: K) -> DiceKey {
        self.index(CowDiceKeyHashed::key(key))
    }

    pub(crate) fn get(&self, key: DiceKey) -> &DiceKeyErased {
        let unpack = DiceKeyUnpacked::unpack(key);
        self.shards[unpack.shard_index as usize]
            .key_by_index
            .get(unpack.index_in_shard as usize)
            .unwrap()
    }
}

mod introspect {
    use crate::impls::key::DiceKey;
    use crate::impls::key_index::DiceKeyIndex;
    use crate::impls::key_index::DiceKeyUnpacked;
    use crate::introspection::graph::AnyKey;
    use crate::HashMap;

    impl DiceKeyIndex {
        pub(crate) fn introspect(&self) -> HashMap<DiceKey, AnyKey> {
            let mut ret = HashMap::default();

            for (shard_index, shard) in self.shards.iter().enumerate() {
                for (index_in_shard, key) in shard.key_by_index.iter().enumerate() {
                    ret.insert(
                        DiceKeyUnpacked {
                            shard_index: shard_index as u32,
                            index_in_shard: index_in_shard as u32,
                        }
                        .pack(),
                        key.introspect(),
                    );
                }
            }

            ret
        }
    }
}

struct DiceKeyUnpacked {
    shard_index: u32,
    index_in_shard: u32, // actually u26
}

impl DiceKeyUnpacked {
    fn pack(&self) -> DiceKey {
        DiceKey {
            index: self.shard_index + DiceKeyIndex::SHARDS * self.index_in_shard,
        }
    }

    fn unpack(key: DiceKey) -> DiceKeyUnpacked {
        let index = key.index;
        DiceKeyUnpacked {
            shard_index: index % DiceKeyIndex::SHARDS,
            index_in_shard: index / DiceKeyIndex::SHARDS,
        }
    }
}

#[cfg(test)]
mod tests {
    use std::cmp;

    use allocative::Allocative;
    use async_trait::async_trait;
    use derive_more::Display;
    use dupe::Dupe;
    use more_futures::cancellation::CancellationContext;

    use crate::api::computations::DiceComputations;
    use crate::api::key::Key;
    use crate::impls::key_index::DiceKeyIndex;
    use crate::impls::key_index::DiceKeyUnpacked;

    #[test]
    fn test_max_index_in_shard() {
        for i in 0..DiceKeyIndex::SHARDS {
            let unpacked = DiceKeyUnpacked {
                shard_index: i,
                index_in_shard: DiceKeyIndex::MAX_INDEX_IN_SHARD,
            };
            let repack = DiceKeyUnpacked::unpack(unpacked.pack());
            assert_eq!(repack.index_in_shard, unpacked.index_in_shard);
            assert_eq!(repack.shard_index, unpacked.shard_index);
        }
    }

    #[test]
    fn test() {
        #[derive(Hash, Clone, Copy, Dupe, Eq, PartialEq, Allocative, Display, Debug)]
        struct TestKey(u64);

        #[async_trait]
        impl Key for TestKey {
            type Value = ();

            async fn compute(
                &self,
                _ctx: &DiceComputations,
                _cancellations: &CancellationContext,
            ) -> Self::Value {
                unimplemented!("not needed")
            }

            fn equality(_x: &Self::Value, _y: &Self::Value) -> bool {
                unimplemented!("not needed")
            }
        }

        let key_index = DiceKeyIndex::default();
        let mut max_index = 0;

        let mut seen_shards = [0; DiceKeyIndex::SHARDS as usize];

        let mut i = 0;
        loop {
            let key = TestKey(i);
            let coin_key = key_index.index_key(key);

            assert_eq!(
                i,
                key_index
                    .get(coin_key)
                    .dupe()
                    .downcast::<TestKey>()
                    .unwrap()
                    .0
            );

            seen_shards[DiceKeyUnpacked::unpack(coin_key).shard_index as usize] += 1;
            max_index = cmp::max(max_index, coin_key.index);
            if seen_shards.iter().all(|&x| x != 0) {
                println!(
                    "seen all shards at: {}; min: {}; max: {}; max_index: {}",
                    i,
                    seen_shards.iter().min().unwrap(),
                    seen_shards.iter().max().unwrap(),
                    max_index,
                );
                break;
            }
            i += 1;
        }

        while i < 100000 {
            let key = TestKey(i);
            let coin_key = key_index.index_key(key);

            assert_eq!(
                i,
                key_index
                    .get(coin_key)
                    .dupe()
                    .downcast::<TestKey>()
                    .unwrap()
                    .0
            );

            seen_shards[DiceKeyUnpacked::unpack(coin_key).shard_index as usize] += 1;
            max_index = cmp::max(max_index, coin_key.index);
            i += 1;
        }

        println!(
            "last: {}; min: {}; max: {}; max_index: {}",
            i,
            seen_shards.iter().min().unwrap(),
            seen_shards.iter().max().unwrap(),
            max_index,
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub(crate) mod cache;
pub(crate) mod core;
pub(crate) mod ctx;
mod dep_trackers;
pub(crate) mod dice;
pub(crate) mod evaluator;
pub(crate) mod events;
mod hash;
pub(crate) mod incremental;
pub(crate) mod key;
mod key_index;
pub(crate) mod opaque;
pub(crate) mod task;
#[cfg(test)]
mod tests;
pub(crate) mod transaction;
pub(crate) mod user_cycle;
pub(crate) mod value;
pub(crate) mod worker;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::marker::PhantomData;

use derivative::Derivative;
use dupe::Dupe;

use crate::api::error::DiceResult;
use crate::api::key::Key;
use crate::api::projection::ProjectionKey;
use crate::impls::ctx::PerComputeCtx;
use crate::impls::key::DiceKey;
use crate::impls::value::MaybeValidDiceValue;

#[derive(Derivative)]
#[derivative(Debug)]
pub(crate) struct OpaqueValueModern<'a, K: Key> {
    derive_from_key: DiceKey,
    #[derivative(Debug = "ignore")]
    derive_from: MaybeValidDiceValue,
    #[derivative(Debug = "ignore")]
    parent_computation: &'a PerComputeCtx,
    ty: PhantomData<K>,
}

impl<'a, K> OpaqueValueModern<'a, K>
where
    K: Key,
{
    pub(crate) fn new(
        parent_computation: &'a PerComputeCtx,
        derive_from_key: DiceKey,
        derive_from: MaybeValidDiceValue,
    ) -> Self {
        Self {
            derive_from_key,
            derive_from,
            parent_computation,
            ty: Default::default(),
        }
    }

    pub(crate) fn projection<P>(&self, projection_key: &P) -> DiceResult<P::Value>
    where
        P: ProjectionKey<DeriveFromKey = K>,
    {
        self.parent_computation.project(
            projection_key,
            self.derive_from_key,
            self.derive_from.dupe(),
        )
    }

    /// Get a value and record parent computation dependency on `K`.
    pub(crate) fn into_value(self) -> K::Value {
        self.parent_computation
            .dep_trackers()
            .record(self.derive_from_key, self.derive_from.validity());

        self.derive_from
            .downcast_maybe_transient::<K::Value>()
            .expect("type mismatch")
            .dupe()
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use allocative::Allocative;
    use async_trait::async_trait;
    use derive_more::Display;
    use more_futures::cancellation::CancellationContext;

    use crate::api::data::DiceData;
    use crate::api::key::Key;
    use crate::impls::dep_trackers::testing::RecordingDepsTrackersExt;
    use crate::impls::dice::DiceModern;
    use crate::impls::key::DiceKey;
    use crate::impls::opaque::OpaqueValueModern;
    use crate::impls::value::DiceKeyValue;
    use crate::impls::value::DiceValidity;
    use crate::impls::value::MaybeValidDiceValue;
    use crate::DiceComputations;
    use crate::HashSet;

    #[derive(Allocative, Clone, Hash, Eq, PartialEq, Debug, Display)]
    struct K;

    #[async_trait]
    impl Key for K {
        type Value = i32;

        async fn compute(
            &self,
            _ctx: &DiceComputations,
            _cancellations: &CancellationContext,
        ) -> Self::Value {
            unimplemented!("test")
        }

        fn equality(_x: &Self::Value, _y: &Self::Value) -> bool {
            unimplemented!("test")
        }
    }

    #[tokio::test]
    async fn opaque_records_deps_when_used() {
        let dice = DiceModern::new(DiceData::new());

        let ctx = dice.updater().commit().await;

        let opaque = OpaqueValueModern::<K>::new(
            &ctx,
            DiceKey { index: 0 },
            MaybeValidDiceValue::new(Arc::new(DiceKeyValue::<K>::new(1)), DiceValidity::Valid),
        );

        assert_eq!(ctx.dep_trackers().recorded_deps(), &HashSet::default());

        assert_eq!(opaque.into_value(), 1);

        assert_eq!(
            ctx.dep_trackers().recorded_deps(),
            &[DiceKey { index: 0 }].into_iter().collect()
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use allocative::Allocative;
use derivative::Derivative;
use dupe::Dupe;
use tokio::sync::oneshot;

use crate::api::error::DiceError;
use crate::api::error::DiceResult;
use crate::api::key::Key;
use crate::api::storage_type::StorageType;
use crate::api::user_data::UserComputationData;
use crate::impls::core::state::CoreStateHandle;
use crate::impls::core::state::StateRequest;
use crate::impls::ctx::BaseComputeCtx;
use crate::impls::ctx::SharedLiveTransactionCtx;
use crate::impls::key::DiceKey;
use crate::impls::value::DiceKeyValue;
use crate::impls::value::DiceValidValue;
use crate::impls::value::DiceValidity;
use crate::impls::value::MaybeValidDiceValue;
use crate::versions::VersionNumber;
use crate::DiceModern;
use crate::HashMap;

// TODO fill this more
#[derive(Allocative)]
pub(crate) struct TransactionUpdater {
    dice: Arc<DiceModern>,
    scheduled_changes: Changes,
    user_data: Arc<UserComputationData>,
}

impl TransactionUpdater {
    pub(crate) fn new(dice: Arc<DiceModern>, user_data: Arc<UserComputationData>) -> Self {
        Self {
            dice: dice.dupe(),
            scheduled_changes: Changes::new(dice),
            user_data,
        }
    }

    /// Records a set of `Key`s as changed so that they, and any dependents will
    /// be recomputed on the next set of requests at the next version.
    pub(crate) fn changed<K, I>(&mut self, changed: I) -> DiceResult<()>
    where
        K: Key,
        I: IntoIterator<Item = K> + Send + Sync + 'static,
    {
        changed
            .into_iter()
            .try_for_each(|k| self.scheduled_changes.change(k, ChangeType::Invalidate))
    }

    /// Records a set of `Key`s as changed to a particular value so that any
    /// dependents will be recomputed on the next set of requests. The
    /// `Key`s themselves will be update to the new value such that they
    /// will not need to be recomputed as long as they aren't recorded to be
    /// `changed` again (or invalidated by other means). Calling this method
    /// does not in anyway alter the types of the key such that they
    /// permanently becomes a special "inject value only" key.
    pub(crate) fn changed_to<K, I>(&mut self, changed: I) -> DiceResult<()>
    where
        K: Key,
        I: IntoIterator<Item = (K, K::Value)> + Send + Sync + 'static,
    {
        changed.into_iter().try_for_each(|(k, new_value)| {
            match MaybeValidDiceValue::new(
                Arc::new(DiceKeyValue::<K>::new(new_value)),
                DiceValidity::Valid,
            )
            .into_valid_value()
            {
                Ok(validated_value) => self.scheduled_changes.change(
                    k,
                    ChangeType::UpdateValue(validated_value, K::storage_type()),
                ),
                Err(_) => Err(DiceError::invalid_change(Arc::new(k))),
            }
        })
    }

    /// Commit the changes registered via 'changed' and 'changed_to' to the current newest version.
    pub(crate) async fn commit(self) -> BaseComputeCtx {
        let user_data = self.user_data.dupe();
        let dice = self.dice.dupe();

        let (transaction, guard) = self.commit_to_state().await;

        BaseComputeCtx::new(transaction, user_data, dice, guard)
    }

    /// Commit the changes registered via 'changed' and 'changed_to' to the current newest version,
    /// replacing the user data with the given set
    pub(crate) async fn commit_with_data(self, extra: UserComputationData) -> BaseComputeCtx {
        let dice = self.dice.dupe();

        let (transaction, guard) = self.commit_to_state().await;

        BaseComputeCtx::new(transaction, Arc::new(extra), dice, guard)
    }

    pub(crate) async fn existing_state(&self) -> BaseComputeCtx {
        let (tx, rx) = oneshot::channel();
        self.dice
            .state_handle
            .request(StateRequest::CurrentVersion { resp: tx });

        let v = rx.await.unwrap();
        let guard = ActiveTransactionGuard::new(v, self.dice.state_handle.dupe());
        let (tx, rx) = oneshot::channel();
        self.dice.state_handle.request(StateRequest::CtxAtVersion {
            version: v,
            guard,
            resp: tx,
        });

        let (transaction, guard) = rx.await.unwrap();
        BaseComputeCtx::new(transaction, self.user_data.dupe(), self.dice.dupe(), guard)
    }

    pub(crate) fn unstable_take(&self) {
        self.dice
            .state_handle
            .request(StateRequest::UnstableDropEverything)
    }

    async fn commit_to_state(self) -> (SharedLiveTransactionCtx, ActiveTransactionGuard) {
        let (tx, rx) = oneshot::channel();
        self.dice.state_handle.request(StateRequest::UpdateState {
            changes: self.scheduled_changes.changes.into_iter().collect(),
            resp: tx,
        });

        let v = rx.await.unwrap();
        let guard = ActiveTransactionGuard::new(v, self.dice.state_handle.dupe());
        let (tx, rx) = oneshot::channel();
        self.dice.state_handle.request(StateRequest::CtxAtVersion {
            guard,
            version: v,
            resp: tx,
        });

        rx.await.unwrap()
    }
}

#[derive(Allocative, Dupe, Clone, Derivative)]
#[derivative(Debug)]
pub(crate) struct ActiveTransactionGuard(Arc<ActiveTransactionGuardInner>);

impl ActiveTransactionGuard {
    pub(crate) fn new(v: VersionNumber, state_handle: CoreStateHandle) -> Self {
        Self(Arc::new(ActiveTransactionGuardInner { v, state_handle }))
    }
}

#[derive(Allocative, Derivative)]
#[derivative(Debug)]
pub(crate) struct ActiveTransactionGuardInner {
    v: VersionNumber,
    #[derivative(Debug = "ignore")]
    state_handle: CoreStateHandle,
}

impl Drop for ActiveTransactionGuardInner {
    fn drop(&mut self) {
        self.state_handle
            .request(StateRequest::DropCtxAtVersion { version: self.v })
    }
}

#[derive(Allocative)]
struct Changes {
    changes: HashMap<DiceKey, ChangeType>,
    dice: Arc<DiceModern>,
}

impl Changes {
    pub(crate) fn new(dice: Arc<DiceModern>) -> Self {
        Self {
            changes: HashMap::default(),
            dice,
        }
    }

    pub(crate) fn change<K: Key>(&mut self, key: K, change: ChangeType) -> DiceResult<()> {
        let key = self.dice.key_index.index_key(key);
        if self.changes.insert(key, change).is_some() {
            Err(DiceError::duplicate(
                self.dice.key_index.get(key).dupe().downcast::<K>().unwrap(),
            ))
        } else {
            Ok(())
        }
    }
}

#[derive(Allocative, Debug)]
pub(crate) enum ChangeType {
    /// Just invalidate the key
    Invalidate,
    /// Update the key to the given value
    UpdateValue(DiceValidValue, StorageType),
    #[cfg(test)]
    /// testing only, set as recheck but not required to rerun
    TestingSoftDirty,
}

#[cfg(test)]
mod tests {

    use allocative::Allocative;
    use assert_matches::assert_matches;
    use async_trait::async_trait;
    use derive_more::Display;
    use more_futures::cancellation::CancellationContext;

    use crate::api::computations::DiceComputations;
    use crate::api::data::DiceData;
    use crate::api::key::Key;
    use crate::impls::dice::DiceModern;
    use crate::impls::key::CowDiceKeyHashed;
    use crate::impls::transaction::ChangeType;
    use crate::versions::VersionNumber;

    #[derive(Allocative, Clone, PartialEq, Eq, Hash, Debug, Display)]
    struct K(usize);

    #[async_trait]
    impl Key for K {
        type Value = usize;

        async fn compute(
            &self,
            _ctx: &DiceComputations,
            _cancellations: &CancellationContext,
        ) -> Self::Value {
            unimplemented!("test")
        }

        fn equality(_x: &Self::Value, _y: &Self::Value) -> bool {
            unimplemented!("test")
        }
    }

    #[test]
    fn changes_are_recorded() -> anyhow::Result<()> {
        let dice = DiceModern::new(DiceData::new());
        let mut updater = dice.updater();

        updater.changed(vec![K(1), K(2)])?;

        updater.changed_to(vec![(K(3), 3), (K(4), 4)])?;

        assert_matches!(
            updater
                .scheduled_changes
                .changes
                .get(&dice.key_index.index(CowDiceKeyHashed::key(K(1)))),
            Some(ChangeType::Invalidate)
        );
        assert_matches!(
            updater
                .scheduled_changes
                .changes
                .get(&dice.key_index.index(CowDiceKeyHashed::key(K(2)))),
            Some(ChangeType::Invalidate)
        );

        assert_matches!(
        updater
            .scheduled_changes
            .changes
            .get(&dice.key_index.index(CowDiceKeyHashed::key(K(3)))),
        Some(ChangeType::UpdateValue(x, _)) if *x.downcast_ref::<usize>().unwrap() == 3
            );

        assert_matches!(
        updater
            .scheduled_changes
            .changes
            .get(&dice.key_index.index(CowDiceKeyHashed::key(K(4)))),
        Some(ChangeType::UpdateValue(x, _)) if *x.downcast_ref::<usize>().unwrap() == 4
            );

        assert!(updater.changed(vec![K(1)]).is_err());

        Ok(())
    }

    #[tokio::test]
    async fn transaction_versions() -> anyhow::Result<()> {
        let dice = DiceModern::new(DiceData::new());
        let mut updater = dice.updater();

        updater.changed(vec![K(1), K(2)])?;

        let ctx = updater.existing_state().await;
        assert_eq!(ctx.get_version(), VersionNumber::new(0));

        let ctx = updater.commit().await;
        assert_eq!(ctx.get_version(), VersionNumber::new(1));

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use dupe::Dupe;

use crate::api::error::DiceError;
use crate::api::error::DiceErrorImpl;
use crate::api::error::DiceResult;
use crate::api::user_data::UserCycleDetector;
use crate::api::user_data::UserCycleDetectorGuard;
use crate::impls::key::DiceKey;
use crate::impls::key::DiceKeyErased;
use crate::impls::key_index::DiceKeyIndex;

pub(crate) struct UserCycleDetectorData(());

impl UserCycleDetectorData {
    pub(crate) fn start_computing_key(
        self,
        k: DiceKey,
        key_index: &DiceKeyIndex,
        detector: Option<&Arc<dyn UserCycleDetector>>,
    ) -> KeyComputingUserCycleDetectorData {
        if let Some(detector) = detector {
            let k_erased = key_index.get(k);
            if let Some(guard) = detector.start_computing_key(k_erased.as_any()) {
                debug!("cycles start key {:?}", k);
                return KeyComputingUserCycleDetectorData::Detecting {
                    k_erased: k_erased.dupe(),
                    k,
                    guard,
                    detector: detector.dupe(),
                };
            }
        }
        KeyComputingUserCycleDetectorData::Untracked
    }

    #[cfg(test)]
    pub(crate) fn testing_new() -> Self {
        Self(())
    }
}

/// User supplied cycle detector
pub(crate) enum KeyComputingUserCycleDetectorData {
    Detecting {
        k_erased: DiceKeyErased,
        k: DiceKey,
        guard: Box<dyn UserCycleDetectorGuard>,
        detector: Arc<dyn UserCycleDetector>,
    },
    Untracked,
}

impl KeyComputingUserCycleDetectorData {
    pub(crate) fn subrequest(&self, k: DiceKey, key_index: &DiceKeyIndex) -> UserCycleDetectorData {
        match self {
            KeyComputingUserCycleDetectorData::Detecting { guard, .. } => {
                guard.add_edge(key_index.get(k).as_any());
            }
            KeyComputingUserCycleDetectorData::Untracked => {}
        }

        UserCycleDetectorData(())
    }

    pub(crate) fn cycle_guard<T: UserCycleDetectorGuard>(&self) -> DiceResult<Option<&T>> {
        match self {
            KeyComputingUserCycleDetectorData::Detecting { guard, .. } => {
                match guard.as_any().downcast_ref() {
                    Some(guard) => Ok(Some(guard)),
                    None => Err(DiceError(Arc::new(
                        DiceErrorImpl::UnexpectedCycleGuardType {
                            expected_type_name: std::any::type_name::<T>().to_owned(),
                            actual_type_name: guard.type_name().to_owned(),
                        },
                    ))),
                }
            }
            KeyComputingUserCycleDetectorData::Untracked => Ok(None),
        }
    }
}

impl Drop for KeyComputingUserCycleDetectorData {
    fn drop(&mut self) {
        match self {
            KeyComputingUserCycleDetectorData::Detecting {
                k_erased,
                k,
                detector,
                ..
            } => {
                debug!("cycles finish key {:?}", k);
                detector.finished_computing_key(k_erased.as_any())
            }
            KeyComputingUserCycleDetectorData::Untracked => {}
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::any::Any;
use std::fmt::Debug;
use std::fmt::Formatter;

use allocative::Allocative;
use dupe::Dupe;

use crate::arc::Arc;
use crate::impls::core::graph::history::CellHistory;
use crate::Key;
use crate::ProjectionKey;

/// Type erased value associated for each Key in Dice. The 'DiceValidValue' only holds valid values
/// and never anything that is transient, or whose dependencies are transient.
#[derive(Allocative, Clone, Dupe)]
pub(crate) struct DiceValidValue(std::sync::Arc<dyn DiceValueDyn>);

impl Debug for DiceValidValue {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DiceValue").finish_non_exhaustive()
    }
}

impl DiceValidValue {
    #[cfg(test)]
    pub(crate) fn downcast_ref<V: Any>(&self) -> Option<&V> {
        self.0.downcast_ref()
    }

    /// Dynamic version of `Key::equality`.
    pub(crate) fn equality(&self, other: &DiceValidValue) -> bool {
        self.0.equality(&*other.0)
    }
}

/// Type erased value that may be transient, or whose dependencies are transient
#[derive(Allocative, Clone, Dupe)]
pub(crate) struct MaybeValidDiceValue {
    value: std::sync::Arc<dyn DiceValueDyn>,
    validity: DiceValidity,
}

impl MaybeValidDiceValue {
    pub(crate) fn new(
        value: std::sync::Arc<dyn DiceValueDyn>,
        deps_validity: DiceValidity,
    ) -> Self {
        let validity = if value.validity() {
            deps_validity
        } else {
            DiceValidity::Transient
        };

        Self { value, validity }
    }

    pub(crate) fn valid(value: DiceValidValue) -> Self {
        Self {
            value: value.0,
            validity: DiceValidity::Valid,
        }
    }

    #[cfg(test)]
    pub(crate) fn transient(value: std::sync::Arc<dyn DiceValueDyn>) -> Self {
        Self {
            value,
            validity: DiceValidity::Transient,
        }
    }

    pub(crate) fn validity(&self) -> DiceValidity {
        self.validity
    }

    pub(crate) fn downcast_maybe_transient<V: Any>(&self) -> Option<&V> {
        self.value.downcast_ref()
    }

    /// Dynamic version of `Key::equality`.
    #[cfg(test)]
    pub(crate) fn equality(&self, other: &DiceValidValue) -> bool {
        self.value.equality(&*other.0)
    }

    pub(crate) fn into_valid_value(self) -> Result<DiceValidValue, MaybeValidDiceValue> {
        match self.validity {
            DiceValidity::Valid => Ok(DiceValidValue(self.value)),
            DiceValidity::Transient => Err(self),
        }
    }
}

/// validity, including based on validity of dependencies
#[derive(Allocative, Clone, Dupe, Copy, PartialEq, Eq, Debug)]
pub(crate) enum DiceValidity {
    Valid,
    /// If the node is invalid or any of its deps are invalid
    Transient,
}

#[derive(Allocative, Clone)]
pub(crate) struct DiceComputedValue {
    value: MaybeValidDiceValue,
    valid: Arc<CellHistory>,
}

impl Dupe for DiceComputedValue {
    // triomphe Arc is dupe
}

impl DiceComputedValue {
    pub(crate) fn new(value: MaybeValidDiceValue, valid: Arc<CellHistory>) -> Self {
        Self { value, valid }
    }

    pub(crate) fn value(&self) -> &MaybeValidDiceValue {
        &self.value
    }

    pub(crate) fn history(&self) -> &CellHistory {
        &self.valid
    }
}

impl Debug for DiceComputedValue {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DiceComputedValue").finish_non_exhaustive()
    }
}

pub(crate) trait DiceValueDyn: Allocative + Any + Send + Sync + 'static {
    fn value_as_any(&self) -> &dyn Any;
    /// Panics if called with incompatible values.
    fn equality(&self, other: &dyn DiceValueDyn) -> bool;
    fn validity(&self) -> bool;
}

impl dyn DiceValueDyn {
    pub(crate) fn downcast_ref<V: Any>(&self) -> Option<&V> {
        self.value_as_any().downcast_ref()
    }
}

#[derive(Allocative)]
pub(crate) struct DiceKeyValue<K: Key> {
    value: K::Value,
}

impl<K> DiceKeyValue<K>
where
    K: Key,
{
    pub(crate) fn new(value: K::Value) -> Self {
        Self { value }
    }
}

impl<K> DiceValueDyn for DiceKeyValue<K>
where
    K: Key,
{
    fn value_as_any(&self) -> &dyn Any {
        &self.value
    }

    fn equality(&self, other: &dyn DiceValueDyn) -> bool {
        K::equality(&self.value, other.downcast_ref().unwrap())
    }

    fn validity(&self) -> bool {
        K::validity(&self.value)
    }
}

#[derive(Allocative)]
pub(crate) struct DiceProjectValue<K: ProjectionKey> {
    value: K::Value,
}

impl<K> DiceProjectValue<K>
where
    K: ProjectionKey,
{
    pub(crate) fn new(value: K::Value) -> Self {
        Self { value }
    }
}

impl<K> DiceValueDyn for DiceProjectValue<K>
where
    K: ProjectionKey,
{
    fn value_as_any(&self) -> &dyn Any {
        &self.value
    }

    fn equality(&self, other: &dyn DiceValueDyn) -> bool {
        K::equality(&self.value, other.downcast_ref().unwrap())
    }

    fn validity(&self) -> bool {
        K::validity(&self.value)
    }
}

#[cfg(test)]
mod testing {
    use std::sync::Arc;

    use crate::impls::value::DiceValidValue;
    use crate::impls::value::DiceValueDyn;
    use crate::impls::value::MaybeValidDiceValue;

    impl DiceValidValue {
        pub(crate) fn testing_new<V: DiceValueDyn>(value: V) -> Self {
            Self(std::sync::Arc::new(value))
        }
    }

    impl MaybeValidDiceValue {
        pub(crate) fn testing_value(&self) -> &Arc<dyn DiceValueDyn> {
            &self.value
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use gazebo::prelude::SliceExt;

use crate::api::storage_type::StorageType;
use crate::arc::Arc;
use crate::impls::cache::SharedCache;
use crate::impls::core::graph::storage::InvalidateKind;
use crate::impls::core::graph::storage::VersionedGraph;
use crate::impls::core::graph::types::VersionedGraphKey;
use crate::impls::core::graph::types::VersionedGraphResult;
use crate::impls::core::versions::VersionEpoch;
use crate::impls::core::versions::VersionTracker;
use crate::impls::key::DiceKey;
use crate::impls::task::dice::DiceTask;
use crate::impls::task::dice::TerminationObserver;
use crate::impls::transaction::ChangeType;
use crate::impls::value::DiceComputedValue;
use crate::impls::value::DiceValidValue;
use crate::introspection::graph::AnyKey;
use crate::introspection::graph::GraphIntrospectable;
use crate::introspection::graph::ModernIntrospectable;
use crate::metrics::Metrics;
use crate::result::CancellableResult;
use crate::result::Cancelled;
use crate::versions::VersionNumber;
use crate::HashMap;

/// Core state of DICE, holding the actual graph and version information
pub(super) struct CoreState {
    version_tracker: VersionTracker,
    graph: VersionedGraph,
    pending_termination_tasks: Vec<DiceTask>,
}

impl CoreState {
    pub(super) fn new() -> Self {
        Self {
            version_tracker: VersionTracker::new(),
            graph: VersionedGraph::new(),
            pending_termination_tasks: Vec::new(),
        }
    }

    pub(super) fn update_state(
        &mut self,
        updates: impl IntoIterator<Item = (DiceKey, ChangeType)>,
    ) -> VersionNumber {
        let version_update = self.version_tracker.write();
        let v = version_update.version();

        let mut changes_recorded = false;
        for (key, change) in updates {
            changes_recorded |= self.graph.invalidate(
                VersionedGraphKey::new(v, key),
                match change {
                    ChangeType::Invalidate => InvalidateKind::ForceDirty,
                    ChangeType::UpdateValue(v, s) => InvalidateKind::Update(v, s),
                    #[cfg(test)]
                    ChangeType::TestingSoftDirty => InvalidateKind::Invalidate,
                },
            );
        }
        if changes_recorded {
            version_update.commit()
        } else {
            version_update.undo()
        }
    }

    pub(super) fn ctx_at_version(&mut self, v: VersionNumber) -> (VersionEpoch, SharedCache) {
        self.version_tracker.at(v)
    }

    pub(super) fn current_version(&self) -> VersionNumber {
        self.version_tracker.current()
    }

    pub(super) fn drop_ctx_at_version(&mut self, v: VersionNumber) {
        if let Some(evicted_cache) = self.version_tracker.drop_at_version(v) {
            self.pending_termination_tasks
                .retain(|task| task.is_pending());
            self.pending_termination_tasks
                .extend(evicted_cache.cancel_pending_tasks());
        }
    }

    pub(super) fn lookup_key(&mut self, key: VersionedGraphKey) -> VersionedGraphResult {
        self.graph.get(key)
    }

    pub(super) fn update_computed(
        &mut self,
        key: VersionedGraphKey,
        epoch: VersionEpoch,
        storage: StorageType,
        value: DiceValidValue,
        deps: Arc<Vec<DiceKey>>,
    ) -> CancellableResult<DiceComputedValue> {
        if self.version_tracker.is_relevant(key.v, epoch) {
            debug!(msg = "update graph entry", k = ?key.k, v = %key.v, v_epoch = %epoch);

            Ok(self.graph.update(key, value, deps, storage).0)
        } else {
            debug!(msg = "update is rejected due to outdated epoch", k = ?key.k, v = %key.v, v_epoch = %epoch);

            Err(Cancelled)
        }
    }

    pub(super) fn get_tasks_pending_cancellation(&mut self) -> Vec<TerminationObserver> {
        self.pending_termination_tasks
            .retain(|task| task.is_pending());

        self.pending_termination_tasks
            .map(|task| task.await_termination())
    }

    pub(super) fn unstable_drop_everything(&mut self) {
        self.version_tracker.write().commit();
        self.graph.last_n.clear();
    }

    pub(super) fn metrics(&self) -> Metrics {
        let mut currently_running_key_count = 0;
        let mut active_transaction_count = 0;

        let currently_active = self.version_tracker.currently_active();
        for active in currently_active {
            active_transaction_count += active.0;
            currently_running_key_count += active.1.active_tasks_count();
        }

        Metrics {
            key_count: self.graph.last_n.len(),
            currently_active_key_count: currently_running_key_count,
            active_transaction_count: active_transaction_count as u32, // probably won't support more than u32 transactions
        }
    }

    pub(super) fn introspection(&self, key_map: HashMap<DiceKey, AnyKey>) -> GraphIntrospectable {
        let graph = self.graph.introspect(key_map.clone());
        let version_data = self.version_tracker.introspect();

        GraphIntrospectable::Modern {
            introspection: ModernIntrospectable {
                graph,
                version_data,
                key_map,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use std::any::Any;

    use allocative::Allocative;
    use async_trait::async_trait;
    use derive_more::Display;
    use dupe::Dupe;
    use futures::FutureExt;
    use more_futures::cancellation::CancellationContext;
    use more_futures::spawner::TokioSpawner;
    use tokio::sync::Semaphore;

    use crate::api::computations::DiceComputations;
    use crate::api::key::Key;
    use crate::arc::Arc;
    use crate::impls::cache::DiceTaskRef;
    use crate::impls::core::graph::history::CellHistory;
    use crate::impls::core::internals::CoreState;
    use crate::impls::key::DiceKey;
    use crate::impls::key::ParentKey;
    use crate::impls::task::dice::DiceTask;
    use crate::impls::task::spawn_dice_task;
    use crate::impls::transaction::ChangeType;
    use crate::impls::value::DiceComputedValue;
    use crate::impls::value::DiceKeyValue;
    use crate::impls::value::DiceValidValue;
    use crate::impls::value::MaybeValidDiceValue;
    use crate::versions::VersionNumber;

    #[test]
    fn update_state_gets_next_version() {
        let mut core = CoreState::new();

        assert_eq!(
            core.update_state([(DiceKey { index: 0 }, ChangeType::Invalidate)]),
            VersionNumber::new(1)
        );

        assert_eq!(
            core.update_state([(DiceKey { index: 1 }, ChangeType::Invalidate)]),
            VersionNumber::new(2)
        );
    }

    #[test]
    fn state_ctx_at_version() {
        let mut core = CoreState::new();
        let v = VersionNumber::new(0);

        let (epoch, ctx) = core.ctx_at_version(v);

        let (epoch1, ctx1) = core.ctx_at_version(v);
        assert!(ctx.ptr_eq(&ctx1));
        assert_eq!(epoch, epoch1);

        // if you drop one, there is still reference so getting the same version should give the
        // same instance of ctx
        core.drop_ctx_at_version(v);
        let (epoch2, ctx2) = core.ctx_at_version(v);
        assert!(ctx.ptr_eq(&ctx2));
        assert_eq!(epoch1, epoch2);

        // drop all references, should give a different ctx instance
        core.drop_ctx_at_version(v);
        core.drop_ctx_at_version(v);
        let (another_epoch, another) = core.ctx_at_version(v);
        assert!(!ctx.ptr_eq(&another));
        assert_ne!(another_epoch, epoch);
    }

    async fn make_completed_task(key: DiceKey, val: usize) -> DiceTask {
        let task = spawn_dice_task(key, &TokioSpawner, &(), |handle| {
            async move {
                handle.finished(DiceComputedValue::new(
                    MaybeValidDiceValue::valid(DiceValidValue::testing_new(
                        DiceKeyValue::<K>::new(val),
                    )),
                    Arc::new(CellHistory::empty()),
                ));

                Box::new(()) as Box<dyn Any + Send>
            }
            .boxed()
        });

        task.depended_on_by(ParentKey::None)
            .not_cancelled()
            .unwrap()
            .await
            .unwrap();

        task
    }

    async fn make_finished_cancelling_task(key: DiceKey) -> DiceTask {
        let finished_cancelling_tasks = spawn_dice_task(key, &TokioSpawner, &(), |handle| {
            async move {
                let _handle = handle;
                futures::future::pending().await
            }
            .boxed()
        });
        finished_cancelling_tasks.cancel();

        finished_cancelling_tasks.await_termination().await;

        finished_cancelling_tasks
    }

    struct BlockCancel(Arc<Semaphore>);

    impl Drop for BlockCancel {
        fn drop(&mut self) {
            self.0.add_permits(1)
        }
    }

    async fn make_yet_to_cancel_tasks(key: DiceKey) -> (DiceTask, BlockCancel, Arc<Semaphore>) {
        let block_cancel = Arc::new(Semaphore::new(0));
        let arrive_cancel = Arc::new(Semaphore::new(0));
        let yet_to_cancel_tasks = spawn_dice_task(key, &TokioSpawner, &(), |handle| {
            let block_cancel = block_cancel.dupe();
            let arrive_cancel = arrive_cancel.dupe();
            async move {
                handle
                    .cancellation_ctx()
                    .critical_section(|| async move {
                        arrive_cancel.add_permits(1);
                        let _guard = block_cancel.acquire().await.unwrap();
                        arrive_cancel.add_permits(1);
                    })
                    .await;

                Box::new(()) as Box<dyn Any + Send>
            }
            .boxed()
        });
        let _started_critical_section = arrive_cancel.acquire().await.unwrap().forget();

        (
            yet_to_cancel_tasks,
            BlockCancel(block_cancel),
            arrive_cancel,
        )
    }

    async fn make_never_cancellable_task(key: DiceKey) -> DiceTask {
        let arrive_never_cancel = Arc::new(Semaphore::new(0));
        let never_cancel_tasks = spawn_dice_task(key, &TokioSpawner, &(), |handle| {
            let arrive_never_cancel = arrive_never_cancel.dupe();
            async move {
                handle
                    .cancellation_ctx()
                    .critical_section(|| async move {
                        arrive_never_cancel.add_permits(1);
                        futures::future::pending().await
                    })
                    .await
            }
            .boxed()
        });

        let _started_critical_section = arrive_never_cancel.acquire().await.unwrap().forget();

        never_cancel_tasks
    }

    #[tokio::test]
    async fn state_tracks_pending_cancellation() {
        let mut core = CoreState::new();
        let v = VersionNumber::new(0);

        let (_epoch, cache) = core.ctx_at_version(v);

        let completed_task1 = make_completed_task(DiceKey { index: 10 }, 1).await;
        let completed_task2 = make_completed_task(DiceKey { index: 20 }, 2).await;

        let finished_cancelling_tasks1 = make_finished_cancelling_task(DiceKey { index: 30 }).await;
        let finished_cancelling_tasks2 = make_finished_cancelling_task(DiceKey { index: 40 }).await;

        let (yet_to_cancel_tasks1, guard1, arrive_cancel1) =
            make_yet_to_cancel_tasks(DiceKey { index: 50 }).await;
        let (yet_to_cancel_tasks2, guard2, arrive_cancel2) =
            make_yet_to_cancel_tasks(DiceKey { index: 60 }).await;

        let never_cancel_tasks1 = make_never_cancellable_task(DiceKey { index: 100500 }).await;

        cache
            .get(DiceKey { index: 1 })
            .testing_insert(completed_task1);
        cache
            .get(DiceKey { index: 2 })
            .testing_insert(completed_task2);
        cache
            .get(DiceKey { index: 3 })
            .testing_insert(finished_cancelling_tasks1);
        cache
            .get(DiceKey { index: 4 })
            .testing_insert(finished_cancelling_tasks2);
        cache
            .get(DiceKey { index: 5 })
            .testing_insert(yet_to_cancel_tasks1);
        cache
            .get(DiceKey { index: 6 })
            .testing_insert(yet_to_cancel_tasks2);
        cache
            .get(DiceKey { index: 7 })
            .testing_insert(never_cancel_tasks1);

        core.drop_ctx_at_version(v);

        assert_eq!(core.get_tasks_pending_cancellation().len(), 3);

        assert!(matches!(
            cache.get(DiceKey { index: 999 }),
            DiceTaskRef::TransactionCancelled
        ));

        // let the cancellable tasks cancel
        drop(guard1);
        drop(guard2);

        // wait for the cancellable tasks to actually cancel
        let _p = arrive_cancel1.acquire().await.unwrap();
        let _p = arrive_cancel2.acquire().await.unwrap();

        let (_epoch, cache) = core.ctx_at_version(v);

        let never_cancel_tasks2 = make_never_cancellable_task(DiceKey { index: 300 }).await;

        cache
            .get(DiceKey { index: 8 })
            .testing_insert(never_cancel_tasks2);

        core.drop_ctx_at_version(v);

        assert_eq!(core.get_tasks_pending_cancellation().len(), 2);
    }

    #[derive(Allocative, Clone, Debug, Display, Eq, PartialEq, Hash)]
    struct K;

    #[async_trait]
    impl Key for K {
        type Value = usize;

        async fn compute(
            &self,
            _ctx: &DiceComputations,
            _cancellations: &CancellationContext,
        ) -> Self::Value {
            unimplemented!("test")
        }

        fn equality(_: &Self::Value, _: &Self::Value) -> bool {
            true
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub(crate) mod graph;
mod internals;
mod processor;
pub(crate) mod state;
pub(crate) mod versions;