hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;
use std::collections::HashSet;

const REVIEW_STATUSES: &[AssetStatus] = &[
    AssetStatus::AdFeedback,
    AssetStatus::FeedbackAddressed,
    AssetStatus::SubmitForReview,
    AssetStatus::_ForReview,
    AssetStatus::QcApproved,
    AssetStatus::AdApproved,
    AssetStatus::PendingFeedback,
    AssetStatus::ClientApproved,
];

#[derive(Debug, Clone, Copy, PartialEq, Hash)]
/// Actionable steps taken on life cycle of an asset which
/// affect its statuses.
pub enum StatusAction {
    /// Right after the creation of the asset.
    BigBang,

    /// UNIMPLEMENTED: Looking into whether the asset's briefs are created or not...
    Genesis,

    /// Adds the given status if it's not present, and removes it otherwise.
    Toggle(AssetStatus),

    /// Marks that the asset's briefs have been created.
    BriefCreated,

    /// Receives number of failed QC items.
    Qc(u16),

    /// After the asset has been uploaded.
    Uploaded,

    /// E.g., when a single `SubTicket` is marked as Addressed by assignees.
    FeedbackAddressed,

    /// E.g., when all `SubTicket`s within a container `Ticket` are closed.
    FeedbackApproved,

    AdFeedback,

    AdApproved,

    ClientFeedback,

    ClientApproved,

    /// After the asset has been published.
    Published,

    /// After the asset has been delivered.
    Delivered,
}

impl StatusAction {
    /// Removes any status from [`REVIEW_STATUSES`] if found in current statuses.
    fn reset_review(current: &[AssetStatus]) -> HashSet<AssetStatus> {
        let review_statuses: HashSet<AssetStatus> = REVIEW_STATUSES.to_vec().into_iter().collect();
        let current: HashSet<AssetStatus> = current.to_vec().into_iter().collect();
        current
            .difference(&review_statuses)
            .map(|s| s.clone())
            .collect()
    }

    /// Adds `AssetStatus::ClientFeedback` after resetting all review statuses from current ones.
    fn client_feedback(&self, current: &[AssetStatus]) -> HashSet<AssetStatus> {
        let mut reset_review = Self::reset_review(current);
        reset_review.insert(AssetStatus::ClientFeedback);
        reset_review
    }

    fn delivered(&self, current: &[AssetStatus]) -> HashSet<AssetStatus> {
        let mut current = target_working_steps_with_extension(
            current,
            AssetStatus::PendingFeedback,
            &[AssetStatus::QcApproved],
        );
        current.remove(&AssetStatus::ClientFeedback);
        current
    }

    /// Returns new set of statuses depending on the event recently taken place,
    /// which should be used to update an asset's statuses after such event.
    pub fn finish(&self, current: &[AssetStatus]) -> HashSet<AssetStatus> {
        match self {
            Self::BigBang => HashSet::from([AssetStatus::NoBrief]),

            // needs to check brief dirs
            Self::Genesis => unimplemented!(),

            Self::Toggle(status) => {
                let mut s: HashSet<AssetStatus> = HashSet::from_iter(current.to_vec());
                match current.contains(&status) {
                    true => {
                        s.remove(status);
                    }
                    false => {
                        s.insert(status.clone());
                    }
                };
                s
            }

            Self::BriefCreated => HashSet::from([AssetStatus::NotStarted]),

            Self::Qc(_failed_items) => unimplemented!(),

            // needs upload step and upload phase
            Self::Uploaded => unimplemented!(),

            Self::FeedbackAddressed => {
                target_working_steps_with_extension(current, AssetStatus::FeedbackAddressed, &[])
            }

            Self::FeedbackApproved => target_working_steps_with_extension(
                current,
                AssetStatus::SubmitForReview,
                &[AssetStatus::QcApproved],
            ),

            Self::AdFeedback => target_working_steps_with_extension(
                current,
                AssetStatus::AdFeedback,
                &[AssetStatus::ClientFeedback],
            ),

            Self::AdApproved => target_working_steps_with_extension(
                current,
                AssetStatus::AdApproved,
                &[AssetStatus::QcApproved],
            ),

            Self::ClientApproved => target_working_steps_with_extension(
                current,
                AssetStatus::ClientApproved,
                &[AssetStatus::QcApproved],
            ),

            Self::ClientFeedback => self.client_feedback(current),

            // needs publish lod and whether it is temp publishing
            Self::Published => unimplemented!(),

            Self::Delivered => self.delivered(current),
        }
    }

    pub fn outcome_as_str(&self, current: &[AssetStatus]) -> Vec<String> {
        self.finish(current).iter().map(|s| s.into()).collect()
    }
}

// ----------------------------------------------------------------------------
/// Working steps, a subset of [`AssetStatus`].
pub const WORKING_STEPS: &[AssetStatus] = &[
    AssetStatus::Blocking,
    AssetStatus::Model,
    AssetStatus::Uv,
    AssetStatus::Texture,
    AssetStatus::Lod,
    AssetStatus::Rig,
];

/// Gets intersection between [`WORKING_STEPS`] and the given `AssetStatus`es.
fn intersect_with_working_steps(statuses: &[AssetStatus]) -> HashSet<AssetStatus> {
    from_slice(statuses)
        .intersection(&from_slice(WORKING_STEPS))
        .map(|s| s.to_owned())
        .collect()
}

/// Adds the `extension` to the intersection of the given `AssetStatus`es and the [`WORKING_STEPS`].
fn working_steps_and_extend(
    statuses: &[AssetStatus],
    extension: &[AssetStatus],
) -> HashSet<AssetStatus> {
    let mut intersect = intersect_with_working_steps(statuses);
    intersect.extend(from_slice(extension));
    intersect
}

/// Inserts the `target` status into the given statuses after retaining both the working steps
/// and the extension statuses.
fn target_working_steps_with_extension(
    statuses: &[AssetStatus],
    target: AssetStatus,
    extension: &[AssetStatus],
) -> HashSet<AssetStatus> {
    let mut extended = working_steps_and_extend(statuses, extension);
    extended.insert(target);
    extended
}

/// Finds the first occurence in the [`WORKING_STEPS`] -- in reverse order --
/// if found in the given `statuses`.
pub fn last_working_step(statuses: &[AssetStatus]) -> Option<AssetStatus> {
    for s in WORKING_STEPS.iter().rev() {
        if statuses.contains(s) {
            return Some(s.clone());
        };
    }
    None
}

// ----------------------------------------------------------------------------
fn from_slice(a: &[AssetStatus]) -> HashSet<AssetStatus> {
    a.to_vec().into_iter().collect()
}