use crate::{
    ebi_framework::{displayable::Displayable, ebi_command::EbiCommand},
    ebi_traits::{
        ebi_trait_event_log_event_attributes::EbiTraitEventLogEventAttributes,
        ebi_trait_semantics::EbiTraitSemantics,
    },
    semantics::semantics::Semantics,
    techniques::{align::Align, resource_utilisation::set_resource_utilisations},
};
use chrono::{DateTime, FixedOffset};
use ebi_objects::{
    Activity, ActivityKey, Attribute, Executions,
    anyhow::{Context, Error, Ok, Result, anyhow},
    ebi_objects::{
        executions::Execution, labelled_petri_net::TransitionIndex, language_of_alignments::Move,
    },
};
use intmap::IntMap;
use process_mining::core::event_data::case_centric::AttributeValue;
use rayon::iter::{IndexedParallelIterator, ParallelIterator};
use std::{
    collections::VecDeque,
    fmt::{Debug, Display},
    hash::Hash,
    sync::{Arc, Mutex},
};

pub trait FindExecutions {
    fn find_executions(
        &mut self,
        log: &mut Box<dyn EbiTraitEventLogEventAttributes>,
    ) -> Result<Executions>;
}

impl FindExecutions for EbiTraitSemantics {
    fn find_executions(
        &mut self,
        log: &mut Box<dyn EbiTraitEventLogEventAttributes>,
    ) -> Result<Executions> {
        match self {
            EbiTraitSemantics::Usize(sem) => sem.find_executions(log),
            EbiTraitSemantics::AutomatonState(sem) => sem.find_executions(log),
            EbiTraitSemantics::Marking(sem) => sem.find_executions(log),
            EbiTraitSemantics::TreeMarking(sem) => sem.find_executions(log),
            EbiTraitSemantics::BPMNMarking(sem) => sem.find_executions(log),
            EbiTraitSemantics::FspolangMarking(sem) => sem.find_executions(log),
        }
    }
}

impl<T, State> FindExecutions for T
where
    T: Semantics<SemState = State, AliState = State> + Send + Sync + ?Sized,
    State: Displayable,
{
    fn find_executions(
        &mut self,
        log: &mut Box<dyn EbiTraitEventLogEventAttributes>,
    ) -> Result<Executions> {
        if self.get_initial_state().is_none() {
            return Err(anyhow!(
                "Model has the empty language, and can therefore not be aligned."
            ));
        }

        log::info!("Compute alignments");
        let progress_bar = EbiCommand::get_progress_bar_ticks(log.number_of_traces());
        let error: Arc<Mutex<Option<Error>>> = Arc::new(Mutex::new(None));
        let resource_key = Arc::new(Mutex::new(ActivityKey::new()));
        let attibute_key = Arc::new(Mutex::new(log.attribute_key().clone()));

        self.translate_using_activity_key(log.activity_key_mut());

        let trace_executions = log
            .par_iter_traces()
            .enumerate()
            .filter_map(|(trace_index, trace)| {
                //align the trace
                let alignment = self.align_trace(&trace);

                progress_bar.inc(1);

                match alignment {
                    Result::Ok((aligned_trace, _)) => {
                        //process the moves of this trace
                        // println!("{:?}", aligned_trace);
                        let c = C::new(trace_index, aligned_trace);
                        match c.alignment_to_executions(self, &log, &resource_key) {
                            Result::Ok(c) => Some(c),
                            Err(err) => {
                                let error = Arc::clone(&error);
                                *error.lock().unwrap() = Some(err);
                                None
                            }
                        }
                    }
                    Err(err) => {
                        //in case of an alignment, pass the error to the main thread
                        let error = Arc::clone(&error);
                        *error.lock().unwrap() = Some(err);
                        None
                    }
                }
            })
            .collect::<Vec<_>>();

        progress_bar.finish_and_clear();

        // println!("{}", self.activity_key());

        //see whether an error was reported
        if let Result::Ok(mutex) = Arc::try_unwrap(error) {
            if let Result::Ok(err) = mutex.into_inner() {
                if let Some(err) = err {
                    return Err(err);
                }
            }
        }

        //merge sort the executions
        let execution_list = ExecutionsSorter::merge_sort(trace_executions);

        //create the result object
        let mut executions = (
            log.activity_key().clone(),
            Arc::try_unwrap(resource_key).unwrap().into_inner().unwrap(),
            Arc::try_unwrap(attibute_key).unwrap().into_inner().unwrap(),
            execution_list,
        )
            .into();

        //set resource utilisations
        set_resource_utilisations(&mut executions)?;

        Ok(executions)
    }
}

struct C {
    trace_index: usize,
    moves: Vec<Move>,
}

impl C {
    fn new(trace_index: usize, moves: Vec<Move>) -> Self {
        Self {
            trace_index: trace_index,
            moves: moves,
        }
    }

    fn alignment_to_executions<T, FS>(
        &self,
        semantics: &T,
        log: &Box<dyn EbiTraitEventLogEventAttributes>,
        resource_key: &Arc<Mutex<ActivityKey>>,
    ) -> Result<VecDeque<Execution>>
    where
        T: Semantics<SemState = FS> + Send + Sync + ?Sized,
        FS: Display + Debug + Clone + Hash + Eq,
    {
        let mut state = semantics
            .get_initial_state()
            .context("The model does not have an initial state.")?;
        let mut executions = VecDeque::with_capacity(self.moves.len());

        for move_index in 0..self.moves.len() {
            match self.moves[move_index] {
                Move::LogMove { .. } => {
                    //logmoves are not linked to transitions and have no executions
                }
                Move::ModelMove {
                    activity,
                    transition,
                } => {
                    let move_index_of_enablement = self.get_enabling_move(move_index, semantics);
                    let mut other_enabled_transitions = semantics.get_enabled_transitions(&state);
                    other_enabled_transitions.retain(|t| *t != transition);

                    let l = other_enabled_transitions.len();
                    executions.push_back(Execution {
                        trace: self.trace_index,
                        move_index,
                        event_attributes: None,
                        activity: Some(activity),
                        also_in_log: false,
                        fired_transition: transition,
                        other_enabled_transitions,
                        move_index_of_enablement,
                        time_of_execution: None,
                        resource: None,
                        resource_utilisation_fired_transition: None,
                        resource_utilisation_other_enabled_transitions: vec![None; l],
                    });

                    semantics.execute_transition(&mut state, transition)?;
                }
                Move::SynchronousMove {
                    activity,
                    transition,
                } => {
                    let move_index_of_enablement = self.get_enabling_move(move_index, semantics);
                    let mut other_enabled_transitions = semantics.get_enabled_transitions(&state);
                    other_enabled_transitions.retain(|t| *t != transition);
                    let event_attributes =
                        Some(self.get_event_attributes(move_index, log).ok_or_else(|| {
                            anyhow!("Could not obtain event attributes for move {}.", move_index)
                        })?);

                    let l = other_enabled_transitions.len();
                    executions.push_back(Execution {
                        trace: self.trace_index,
                        move_index,
                        event_attributes,
                        activity: Some(activity),
                        also_in_log: true,
                        fired_transition: transition,
                        other_enabled_transitions,
                        move_index_of_enablement,
                        time_of_execution: self.get_time(Some(move_index), log).cloned(),
                        resource: self.get_resource(Some(move_index), log, resource_key),
                        resource_utilisation_fired_transition: None,
                        resource_utilisation_other_enabled_transitions: vec![None; l],
                    });

                    semantics.execute_transition(&mut state, transition)?;
                }
                Move::SilentMove { transition } => {
                    let move_index_of_enablement = self.get_enabling_move(move_index, semantics);
                    let mut other_enabled_transitions = semantics.get_enabled_transitions(&state);
                    other_enabled_transitions.retain(|t| *t != transition);

                    let l = other_enabled_transitions.len();
                    executions.push_back(Execution {
                        trace: self.trace_index,
                        move_index,
                        event_attributes: None,
                        activity: None,
                        also_in_log: false,
                        fired_transition: transition,
                        other_enabled_transitions,
                        move_index_of_enablement,
                        time_of_execution: None,
                        resource: None,
                        resource_utilisation_fired_transition: None,
                        resource_utilisation_other_enabled_transitions: vec![None; l],
                    });

                    semantics.execute_transition(&mut state, transition)?;
                }
            }
        }

        Ok(executions)
    }

    fn get_time<'a>(
        &self,
        move_index: Option<usize>,
        log: &'a Box<dyn EbiTraitEventLogEventAttributes>,
    ) -> Option<&'a DateTime<FixedOffset>> {
        let event_index = self.get_event_index(move_index?)?;
        log.get_event_time(self.trace_index, event_index)
    }

    fn get_resource<'a>(
        &self,
        move_index: Option<usize>,
        log: &'a Box<dyn EbiTraitEventLogEventAttributes>,
        resource_key: &Arc<Mutex<ActivityKey>>,
    ) -> Option<Activity> {
        let event_index = self.get_event_index(move_index?)?;
        let resource_string = log.get_event_resource(self.trace_index, event_index)?;

        Some(
            resource_key
                .lock()
                .as_mut()
                .unwrap()
                .process_activity(resource_string),
        )
    }

    fn get_event_attributes<'a>(
        &self,
        move_index: usize,
        log: &'a Box<dyn EbiTraitEventLogEventAttributes>,
    ) -> Option<IntMap<Attribute, AttributeValue>> {
        let event_index = self.get_event_index(move_index)?;
        log.get_event_attributes(self.trace_index, event_index)
    }

    fn get_event_index(&self, move_index: usize) -> Option<usize> {
        let mut event_index = 0;
        let mut last = false;
        for movee in self.moves.iter().take(move_index + 1) {
            match movee {
                Move::LogMove { .. } | Move::SynchronousMove { .. } => {
                    event_index += 1;
                    last = true;
                }
                _ => last = false,
            }
        }
        if last { Some(event_index - 1) } else { None }
    }

    /**
     * Get the last move that enabled the move at the given index.
     */
    fn get_enabling_move<T, FS>(&self, move_index: usize, semantics: &T) -> Option<usize>
    where
        T: Semantics<SemState = FS> + Send + Sync + ?Sized,
        FS: Display + Debug + Clone + Hash + Eq,
    {
        let transition_that_may_get_enabled = self.moves[move_index].get_transition().unwrap();

        //first, figure out when this move's transition was last enabled
        let (mut result, mut state) =
            MoveEnabled::start(semantics, transition_that_may_get_enabled)?;
        for (move_index2, move2) in self.moves.iter().take(move_index).enumerate() {
            if let Some(transition2) = move2.get_transition() {
                //sync, model or silent move
                result.execute_transition(
                    semantics,
                    &mut state,
                    transition_that_may_get_enabled,
                    transition2,
                    move_index2,
                );
            } else {
                //skip log move
            }
        }
        result.finalise()
    }
}

#[derive(Debug, Copy, Clone)]
enum MoveEnabled {
    FromStartOfTrace,
    AsResultOfMove(usize),
    NotEnabled,
}

impl MoveEnabled {
    fn start<T, FS>(
        semantics: &T,
        transition_that_may_get_enabled: TransitionIndex,
    ) -> Option<(Self, FS)>
    where
        T: Semantics<SemState = FS> + Send + Sync + ?Sized,
        FS: Display + Debug + Clone + Hash + Eq,
    {
        let state = semantics
            .get_initial_state()
            .expect("there is no initial state");

        if semantics
            .get_enabled_transitions(&state)
            .contains(&transition_that_may_get_enabled)
        {
            Some((Self::FromStartOfTrace, state))
        } else {
            Some((Self::NotEnabled, state))
        }
    }

    fn execute_transition<T, FS>(
        &mut self,
        semantics: &T,
        state: &mut FS,
        transition_that_may_get_enabled: TransitionIndex,
        transition: TransitionIndex,
        move_index: usize,
    ) where
        T: Semantics<SemState = FS> + Send + Sync + ?Sized,
        FS: Display + Debug + Clone + Hash + Eq,
    {
        semantics
            .execute_transition(state, transition)
            .expect("transition was not enabled and nevertheless fired");

        let now_enabled = semantics
            .get_enabled_transitions(&state)
            .contains(&transition_that_may_get_enabled);
        *self = match (now_enabled, &self) {
            (true, MoveEnabled::FromStartOfTrace) => MoveEnabled::FromStartOfTrace,
            (true, MoveEnabled::AsResultOfMove(x)) => MoveEnabled::AsResultOfMove(*x),
            (true, MoveEnabled::NotEnabled) => MoveEnabled::AsResultOfMove(move_index),
            (false, MoveEnabled::FromStartOfTrace) => MoveEnabled::NotEnabled,
            (false, MoveEnabled::AsResultOfMove(_)) => MoveEnabled::NotEnabled,
            (false, MoveEnabled::NotEnabled) => MoveEnabled::NotEnabled,
        };
    }

    fn finalise(self) -> Option<usize> {
        match self {
            MoveEnabled::FromStartOfTrace => None,
            MoveEnabled::AsResultOfMove(move_index) => Some(move_index),
            MoveEnabled::NotEnabled => {
                panic!("transition was not enabled and it nevertheless fired")
            }
        }
    }
}

struct ExecutionsSorter {}

impl ExecutionsSorter {
    fn merge_sort(mut traces: Vec<VecDeque<Execution>>) -> Vec<Execution> {
        let number_of_executions = traces.iter().map(|t| t.len()).sum();
        let mut result = Vec::with_capacity(number_of_executions);

        log::info!("Sorting executions");

        //invariant: the first move of every trace has a timestamp

        //establish the invariant: sort all moves without timestamps
        for trace_index in 0..traces.len() {
            let trace = &mut traces[trace_index];
            while !trace.is_empty() && trace[0].time_of_execution.is_none() {
                result.push(trace.pop_front().unwrap());
            }
        }

        //initialise first-timestamps
        let mut first_timestamps = (0..traces.len())
            .map(|trace_index| Self::get_first_timestamp(&traces[trace_index]).cloned())
            .collect::<Vec<_>>();

        loop {
            if let Some((trace_index, _)) = first_timestamps
                .iter()
                .enumerate()
                .filter_map(|(trace_index, first_timestamp)| {
                    if let Some(first) = first_timestamp {
                        Some((trace_index, first))
                    } else {
                        None
                    }
                })
                .min_by(|a, b| {
                    let c = a.1.cmp(&b.1);
                    if !c.is_eq() { c } else { a.0.cmp(&b.0) }
                })
            {
                //process the trace with the lowest timestamp

                //first, add the first execution
                result.push(traces[trace_index].pop_front().unwrap());

                //re-establish the invariant: add non-timestamped moves
                let trace = &mut traces[trace_index];
                while !trace.is_empty() && trace[0].time_of_execution.is_none() {
                    result.push(trace.pop_front().unwrap());
                }

                //update the first timestamp
                first_timestamps[trace_index] =
                    Self::get_first_timestamp(&traces[trace_index]).cloned();
            } else {
                //no more timestamps, just append the result
                result.extend(traces.into_iter().flatten());
                return result;
            }
        }
    }

    fn get_first_timestamp(trace: &VecDeque<Execution>) -> Option<&DateTime<FixedOffset>> {
        for execution in trace {
            if execution.time_of_execution.is_some() {
                return execution.time_of_execution.as_ref();
            }
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        ebi_framework::trait_importers::ToSemanticsTrait,
        ebi_traits::ebi_trait_event_log_event_attributes::EbiTraitEventLogEventAttributes,
        techniques::executions::FindExecutions,
    };
    use ebi_objects::{
        LabelledPetriNet, StochasticDeterministicFiniteAutomaton,
        StochasticNondeterministicFiniteAutomaton, ebi_arithmetic::is_exact_globally,
        ebi_objects::event_log_event_attributes::EventLogEventAttributes,
    };
    use std::fs;

    #[test]
    fn executions() {
        let fin = fs::read_to_string("testfiles/a-b.xes").unwrap();
        let log = fin.parse::<EventLogEventAttributes>().unwrap();

        let fin2 = fs::read_to_string("testfiles/a-b-c-livelock.sdfa").unwrap();
        let mut model = fin2
            .parse::<StochasticDeterministicFiniteAutomaton>()
            .unwrap();

        let out = fs::read_to_string("testfiles/a-b.exs").unwrap();

        let mut log2: Box<dyn EbiTraitEventLogEventAttributes> = Box::new(log);
        let x = model.find_executions(&mut log2).unwrap();

        assert_eq!(out, x.to_string());
    }

    #[test]
    fn svn60() {
        let fin = fs::read_to_string("testfiles/svn60.xes").unwrap();
        let log = fin.parse::<EventLogEventAttributes>().unwrap();
        let mut log: Box<dyn EbiTraitEventLogEventAttributes> = Box::new(log);

        let fin2 = fs::read_to_string("testfiles/svn60.lpn").unwrap();
        let lpn = fin2.parse::<LabelledPetriNet>().unwrap();

        let mut sem = lpn.to_semantics_trait();
        let executions = sem.find_executions(&mut log).unwrap();

        println!("{}", executions);

        let fin3 = fs::read_to_string("testfiles/svn60.lpn.exs").unwrap();

        if !is_exact_globally() {
            //string output looks slightly different in approximate mode; thus this will fail
            return;
        }

        assert_eq!(executions.to_string(), fin3);
    }

    #[test]
    fn snfa_executions() {
        let fin = fs::read_to_string("testfiles/simple_log_markovian_abstraction.xes").unwrap();
        let log = fin.parse::<EventLogEventAttributes>().unwrap();

        let fin2 = fs::read_to_string("testfiles/aa-ab-ba.snfa").unwrap();
        let mut model = fin2
            .parse::<StochasticNondeterministicFiniteAutomaton>()
            .unwrap();

        let mut log2: Box<dyn EbiTraitEventLogEventAttributes> = Box::new(log);
        model.find_executions(&mut log2).unwrap();
    }
}
