use crate::{
    ebi_framework::{
        ebi_input::EbiInput, ebi_trait::FromEbiTraitObject, ebi_trait_object::EbiTraitObject,
        trait_importers::ToSemanticsTrait,
    },
    semantics::{
        finite_stochastic_language_semantics::FiniteStochasticLanguageSemantics,
        finite_stochastic_partially_ordered_language_semantics::FspolangMarking,
        labelled_petri_net_semantics::LPNMarking, semantics::Semantics,
    },
};
use ebi_objects::{
    ActivityKey, AutomatonState, BusinessProcessModelAndNotation, DeterministicFiniteAutomaton,
    DirectlyFollowsGraph, DirectlyFollowsModel, EventLog, EventLogPython, EventLogTraceAttributes,
    EventLogXes, FiniteLanguage, FiniteStochasticLanguage,
    FiniteStochasticPartiallyOrderedLanguage, HasActivityKey, LabelledPetriNet, LolaNet,
    PetriNetMarkupLanguage, ProcessTree, ProcessTreeMarkupLanguage,
    StochasticBusinessProcessModelAndNotation, StochasticDeterministicFiniteAutomaton,
    StochasticDirectlyFollowsModel, StochasticLabelledPetriNet,
    StochasticNondeterministicFiniteAutomaton, StochasticProcessTree, TranslateActivityKey,
    anyhow::{Result, anyhow},
    ebi_bpmn::BPMNMarking,
    ebi_objects::{
        compressed_event_log::CompressedEventLog,
        compressed_event_log_trace_attributes::CompressedEventLogTraceAttributes,
        event_log_csv::EventLogCsv, event_log_ocel::EventLogOcel, process_tree::TreeMarking,
    },
};

#[macro_export]
macro_rules! trait_definition_finalisation {
    () => {
        "Each deadlock is a final state, and each final state is a deadlock. It is not necessary that a final state is always reachable, and thus you may end up in a livelock.
        \\begin{tabularx}{\\linewidth}{XXccc}
            \\toprule
            \\rotatebox{90}{File type} & \\rotatebox{90}{use} & \\rotatebox{90}{stochastic} & \\rotatebox{90}{deterministic} & \\rotatebox{90}{silent transitions}\\\\
            \\midrule
            \\hyperref[trait:semantics]{semantics} & state space traversal & no & no & yes\\\\
            \\hyperref[trait:stochastic semantics]{stochastic semantics} & stochastic state space traversal & yes & no & yes\\\\
            \\hyperref[trait:stochastic deterministic semantics]{stochastic deterministic semantics} & stochastic state space traversal, where in each state, each label is enabled at most once & yes & yes & no\\\\
            \\hyperref[trait:stochastic partially ordered semantics]{trait:stochastic partially ordered semantics} & state space traversal, with partially ordered stochastics & yes & no & yes\\\\
            \\bottomrule
        \\end{tabularx}
        As a developer, using a trait may avoid a conversion. Use objects if this conversion is acceptable, or when the object needs to be updated."
    };
}

pub const TRAIT_DEFINITION_LATEX: &str = concat!(
    "The trait ``semantics'' allows a state space can be traversed.",
    trait_definition_finalisation!()
);

/// This is a wrapper enum in order to be able to implement algorithms that are agnostic of the marking/state type, amongst other things.
/// This enum does not allow access to the underlying Semantics trait methods.
/// For these methods, such as 'get_initial_state' and 'get_enabled_transitions', please access the struct directly.
pub enum EbiTraitSemantics {
    Usize(Box<dyn Semantics<SemState = usize, AliState = usize>>),
    AutomatonState(Box<dyn Semantics<SemState = AutomatonState, AliState = AutomatonState>>),
    Marking(Box<dyn Semantics<SemState = LPNMarking, AliState = LPNMarking>>),
    TreeMarking(Box<dyn Semantics<SemState = TreeMarking, AliState = TreeMarking>>),
    BPMNMarking(Box<dyn Semantics<SemState = BPMNMarking, AliState = BPMNMarking>>),
    FspolangMarking(Box<dyn Semantics<SemState = FspolangMarking, AliState = FspolangMarking>>),
}

impl HasActivityKey for EbiTraitSemantics {
    fn activity_key(&self) -> &ActivityKey {
        match self {
            EbiTraitSemantics::Marking(semantics) => semantics.activity_key(),
            EbiTraitSemantics::Usize(semantics) => semantics.activity_key(),
            EbiTraitSemantics::AutomatonState(semantics) => semantics.activity_key(),
            EbiTraitSemantics::TreeMarking(semantics) => semantics.activity_key(),
            EbiTraitSemantics::BPMNMarking(semantics) => semantics.activity_key(),
            EbiTraitSemantics::FspolangMarking(semantics) => semantics.activity_key(),
        }
    }

    fn activity_key_mut(&mut self) -> &mut ActivityKey {
        match self {
            EbiTraitSemantics::Marking(semantics) => semantics.activity_key_mut(),
            EbiTraitSemantics::Usize(semantics) => semantics.activity_key_mut(),
            EbiTraitSemantics::AutomatonState(semantics) => semantics.activity_key_mut(),
            EbiTraitSemantics::TreeMarking(semantics) => semantics.activity_key_mut(),
            EbiTraitSemantics::BPMNMarking(semantics) => semantics.activity_key_mut(),
            EbiTraitSemantics::FspolangMarking(semantics) => semantics.activity_key_mut(),
        }
    }
}

impl TranslateActivityKey for EbiTraitSemantics {
    fn translate_using_activity_key(&mut self, to_activity_key: &mut ActivityKey) {
        match self {
            EbiTraitSemantics::Marking(semantics) => {
                semantics.translate_using_activity_key(to_activity_key)
            }
            EbiTraitSemantics::Usize(semantics) => {
                semantics.translate_using_activity_key(to_activity_key)
            }
            EbiTraitSemantics::AutomatonState(semantics) => {
                semantics.translate_using_activity_key(to_activity_key)
            }
            EbiTraitSemantics::TreeMarking(semantics) => {
                semantics.translate_using_activity_key(to_activity_key)
            }
            EbiTraitSemantics::BPMNMarking(semantics) => {
                semantics.translate_using_activity_key(to_activity_key)
            }
            EbiTraitSemantics::FspolangMarking(semantics) => {
                semantics.translate_using_activity_key(to_activity_key)
            }
        }
    }
}

impl FromEbiTraitObject for EbiTraitSemantics {
    fn from_trait_object(object: EbiInput) -> Result<Box<Self>> {
        match object {
            EbiInput::Trait(EbiTraitObject::Semantics(e), _) => Ok(Box::new(e)),
            _ => Err(anyhow!(
                "Cannot read {} {} as a semantics.",
                object.get_type().get_article(),
                object.get_type()
            )),
        }
    }
}

macro_rules! via_lang {
    ($t:ident) => {
        impl ToSemanticsTrait for $t {
            fn to_semantics_trait(self) -> EbiTraitSemantics {
                Into::<FiniteLanguage>::into(self).to_semantics_trait()
            }
        }
    };
}
via_lang!(CompressedEventLog);
via_lang!(CompressedEventLogTraceAttributes);
via_lang!(EventLog);
via_lang!(EventLogTraceAttributes);
via_lang!(EventLogXes);
via_lang!(EventLogCsv);
via_lang!(EventLogOcel);
via_lang!(EventLogPython);

impl ToSemanticsTrait for BusinessProcessModelAndNotation {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::BPMNMarking(Box::new(self))
    }
}

impl ToSemanticsTrait for StochasticBusinessProcessModelAndNotation {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::BPMNMarking(Box::new(self))
    }
}

impl ToSemanticsTrait for DeterministicFiniteAutomaton {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::AutomatonState(Box::new(self))
    }
}

impl ToSemanticsTrait for LolaNet {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        self.0.to_semantics_trait()
    }
}

impl ToSemanticsTrait for StochasticProcessTree {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::TreeMarking(Box::new(self))
    }
}

impl ToSemanticsTrait for LabelledPetriNet {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::Marking(Box::new(self))
    }
}

impl ToSemanticsTrait for PetriNetMarkupLanguage {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        self.0.to_semantics_trait()
    }
}

impl ToSemanticsTrait for StochasticLabelledPetriNet {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::Marking(Box::new(self))
    }
}

impl ToSemanticsTrait for StochasticDirectlyFollowsModel {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::AutomatonState(Box::new(self))
    }
}

impl ToSemanticsTrait for DirectlyFollowsGraph {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::AutomatonState(Box::new(self))
    }
}

impl ToSemanticsTrait for DirectlyFollowsModel {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::AutomatonState(Box::new(self))
    }
}

impl ToSemanticsTrait for StochasticDeterministicFiniteAutomaton {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::AutomatonState(Box::new(self))
    }
}

impl ToSemanticsTrait for StochasticNondeterministicFiniteAutomaton {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::AutomatonState(Box::new(self))
    }
}

impl ToSemanticsTrait for FiniteLanguage {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        Into::<DeterministicFiniteAutomaton>::into(self).to_semantics_trait()
    }
}

impl ToSemanticsTrait for FiniteStochasticLanguage {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::Usize(Box::new(FiniteStochasticLanguageSemantics::from_language(
            &self,
        )))
    }
}

impl ToSemanticsTrait for FiniteStochasticPartiallyOrderedLanguage {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::FspolangMarking(Box::new(self))
    }
}

impl ToSemanticsTrait for ProcessTreeMarkupLanguage {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        let lpn: LabelledPetriNet = self.into();
        EbiTraitSemantics::Marking(Box::new(lpn))
    }
}

impl ToSemanticsTrait for ProcessTree {
    fn to_semantics_trait(self) -> EbiTraitSemantics {
        EbiTraitSemantics::TreeMarking(Box::new(self))
    }
}
