use super::command;
use ccutils::sync::{ArcRwLock, IntoArcRwLock};
use rune::Any;
use std::sync::Arc;

use crate::prelude::*;

pub(crate) struct ContextInner {
    activities: Vec<ActivityBox>,
}

impl ContextInner {
    pub(crate) fn push_activity<TActivity: Activity + 'static>(&mut self, activity: TActivity) {
        self.activities.push(Box::new(activity));
    }
}

#[derive(Any, Clone)]
#[rune(item = ::stories)]
pub(crate) struct Context {
    pub(crate) inner: ArcRwLock<ContextInner>,
    reporter: Reporter,
    runtime: Arc<tokio::runtime::Runtime>,
}

impl Context {
    /// Create a new context with the given tokio runtime
    pub(crate) fn new(runtime: Arc<tokio::runtime::Runtime>, reporter: Reporter) -> Context {
        Context {
            inner: ContextInner {
                activities: Default::default(),
            }
            .into_arc_rw_lock(),
            reporter,
            runtime,
        }
    }
    /// Report an  error with the given message, filename and line number.
    /// There here argument can be created with the `here!()` macro.
    ///
    /// ```rune
    /// ctx.report_error("An error has occurred.", here!());
    /// ```
    #[rune::function]
    pub fn report_error(&self, msg: String, here: Here) {
        self.reporter.report_error(msg, here);
    }
    /// This start executing a command.
    ///
    /// For example:
    /// ```rune
    /// let cmd = ctx.rune("ls", ["-l"], here!())?;
    /// ```
    #[rune::function]
    fn run_command(
        &mut self,
        command_name: String,
        args: Vec<String>,
        here: Here,
    ) -> RResult<command::Command> {
        let spawn_r = command::Command::new(&self, command_name.clone(), args, here.clone());
        match spawn_r {
            Ok(v) => Ok(v),
            Err(e) => {
                self.reporter.report_error(
                    format!("Failure to start command '{}'.", command_name),
                    here,
                );
                Err(e)?
            }
        }
    }
    pub(crate) fn runtime_ref(&self) -> &Arc<tokio::runtime::Runtime> {
        &self.runtime
    }
    pub fn is_successful(&self) -> bool {
        !self.reporter.has_errors()
    }
    pub fn reporter_ref(&self) -> &Reporter {
        &self.reporter
    }
    pub(crate) async fn finalise_activities(&self, timeout: std::time::Duration) {
        let mut activities = vec![];
        std::mem::swap(&mut activities, &mut self.inner.write().unwrap().activities);
        let mut vec = Vec::new();
        for act in activities.into_iter() {
            let f = act.finalise(&self.reporter, timeout);
            let f = tokio::time::timeout(3 * timeout, f);
            vec.push(f);
        }
        futures_util::future::join_all(vec).await;
    }
}
