use crate::{
    ebi_framework::displayable::Displayable,
    semantics::{labelled_petri_net_semantics::LPNMarking, semantics::Semantics},
    techniques::livelock::IsPartOfLivelock,
};
use ebi_objects::{
    AutomatonState, DeterministicFiniteAutomaton, DirectlyFollowsGraph, DirectlyFollowsModel,
    EventLog, EventLogPython, EventLogTraceAttributes, EventLogXes, FiniteLanguage,
    FiniteStochasticLanguage, FiniteStochasticPartiallyOrderedLanguage, LabelledPetriNet,
    NumberOfTraces, ProcessTree, StochasticDeterministicFiniteAutomaton,
    StochasticDirectlyFollowsModel, StochasticLabelledPetriNet,
    StochasticNondeterministicFiniteAutomaton, StochasticProcessTree,
    anyhow::Result,
    ebi_arithmetic::ebi_number::Zero,
    ebi_objects::{
        event_log_csv::EventLogCsv, event_log_event_attributes::EventLogEventAttributes,
        event_log_ocel::EventLogOcel, process_tree::TreeMarking,
    },
};

pub trait AnyTraces {
    type LivState: Displayable;

    /**
     * Returns whether the object has any traces.
     */
    fn any_traces(&self) -> Result<bool>;
}

impl AnyTraces for ProcessTree {
    type LivState = TreeMarking;

    fn any_traces(&self) -> Result<bool> {
        Ok(self.get_initial_state().is_none()) //an empty tree has no traces, otherwise a tree has traces
    }
}

impl AnyTraces for StochasticProcessTree {
    type LivState = TreeMarking;

    fn any_traces(&self) -> Result<bool> {
        Ok(self.get_initial_state().is_none()) //an empty tree has no traces, otherwise a tree has traces
    }
}

impl AnyTraces for EventLogOcel {
    type LivState = usize;

    fn any_traces(&self) -> Result<bool> {
        Ok(
            !Self::get_relevant_objects(&self.rust4pm_log.objects, &self.case_object_type)
                .is_empty(),
        )
    }
}

macro_rules! lang {
    ($t:ident) => {
        impl AnyTraces for $t {
            type LivState = usize;

            fn any_traces(&self) -> Result<bool> {
                Ok(!self.number_of_traces().is_zero())
            }
        }
    };
}

macro_rules! lpn {
    ($t:ident) => {
        impl AnyTraces for $t {
            type LivState = LPNMarking;

            fn any_traces(&self) -> Result<bool> {
                if let Some(initial_state) = self.get_initial_state() {
                    //If the initial state is not a livelock, the model has traces
                    return Ok(!self.is_state_part_of_livelock(&initial_state)?);
                } else {
                    //if there is no initial state, then the model has no traces.
                    return Ok(false);
                }
            }
        }
    };
}

macro_rules! dfm {
    ($t:ident) => {
        impl AnyTraces for $t {
            type LivState = AutomatonState;

            fn any_traces(&self) -> Result<bool> {
                if let Some(initial_state) = self.get_initial_state() {
                    //If the initial state is not a livelock, the model has traces.
                    return Ok(!self.is_state_part_of_livelock(&initial_state)?);
                } else {
                    //if there is no initial state, then the model has no traces.
                    return Ok(false);
                }
            }
        }
    };
}

lang!(FiniteLanguage);
lang!(FiniteStochasticLanguage);
lang!(FiniteStochasticPartiallyOrderedLanguage);
lang!(EventLog);
lang!(EventLogCsv);
lang!(EventLogEventAttributes);
lang!(EventLogPython);
lang!(EventLogTraceAttributes);
lang!(EventLogXes);
lpn!(LabelledPetriNet);
lpn!(StochasticLabelledPetriNet);
dfm!(DeterministicFiniteAutomaton);
dfm!(StochasticDeterministicFiniteAutomaton);
dfm!(StochasticNondeterministicFiniteAutomaton);
dfm!(DirectlyFollowsModel);
dfm!(StochasticDirectlyFollowsModel);
dfm!(DirectlyFollowsGraph);

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

    use ebi_objects::{
        DeterministicFiniteAutomaton, FiniteStochasticLanguage, LabelledPetriNet, ProcessTree,
        StochasticDeterministicFiniteAutomaton, StochasticLabelledPetriNet,
    };

    use crate::techniques::any_traces::AnyTraces;

    #[test]
    fn any_traces_tree() {
        let fin = fs::read_to_string("testfiles/empty.ptree").unwrap();
        let tree = fin.parse::<ProcessTree>().unwrap();

        assert!(tree.any_traces().unwrap());
    }

    #[test]
    fn any_traces_lpn() {
        let fin = fs::read_to_string("testfiles/a-a-livelock.lpn").unwrap();
        let lpn = fin.parse::<LabelledPetriNet>().unwrap();

        assert!(!lpn.any_traces().unwrap());
    }

    #[test]
    fn any_traces_slpn() {
        let fin = fs::read_to_string("testfiles/infinite_bs.slpn").unwrap();
        let slpn = fin.parse::<StochasticLabelledPetriNet>().unwrap();

        assert!(slpn.any_traces().unwrap());
    }

    #[test]
    fn any_traces_dfa() {
        let fin = fs::read_to_string("testfiles/empty.dfa").unwrap();
        let dfa = fin.parse::<DeterministicFiniteAutomaton>().unwrap();

        assert!(!dfa.any_traces().unwrap());
    }

    #[test]
    fn any_traces_sdfa() {
        let fin = fs::read_to_string("testfiles/empty.sdfa").unwrap();
        let sdfa = fin
            .parse::<StochasticDeterministicFiniteAutomaton>()
            .unwrap();

        assert!(!sdfa.any_traces().unwrap());
    }

    #[test]
    fn any_traces_slang() {
        let fin = fs::read_to_string("testfiles/empty.slang").unwrap();
        let slang = fin.parse::<FiniteStochasticLanguage>().unwrap();

        assert!(!slang.any_traces().unwrap());
    }
}
