use super::ebi_command_sample::{self, SAMPLED_OBJECT_INPUTS};
use crate::{
    EbiInputTypeEnum,
    ebi_framework::{
        ebi_command::EbiCommand,
        ebi_input::{EbiInput, EbiInputType},
        ebi_output::{EbiOutput, EbiOutputType},
        ebi_trait::EbiTrait,
        ebi_trait_object::EbiTraitObject,
    },
    ebi_traits::{
        ebi_trait_finite_stochastic_language::EbiTraitFiniteStochasticLanguage,
        ebi_trait_queriable_stochastic_language::EbiTraitQueriableStochasticLanguage,
    },
    techniques::{
        chi_square_stochastic_conformance::ChiSquareStochasticConformance,
        earth_movers_stochastic_conformance::EarthMoversStochasticConformance,
        entropic_relevance::EntropicRelvance,
        gain_precision_recall::PotentialGainRecallPrecision,
        hellinger_stochastic_conformance::HellingerStochasticConformance,
        jensen_shannon_stochastic_conformance::JensenShannonStochasticConformance,
        partially_ordered_earth_movers_stochastic_conformance::PartiallyOrderedEarthMoversStochasticConformance,
        stochastic_markovian_abstraction::AbstractMarkovian,
        stochastic_markovian_abstraction_conformance::{
            DistanceMeasure, StochasticMarkovianConformance,
        },
        unit_earth_movers_stochastic_conformance::UnitEarthMoversStochasticConformance,
    },
};
use ebi_objects::{
    EbiObject, EbiObjectType,
    anyhow::{Context, anyhow},
    ebi_arithmetic::{ConstFraction, Fraction},
    ebi_objects::stochastic_deterministic_finite_automaton::StochasticDeterministicFiniteAutomaton,
};
use strum::VariantNames;

pub const EBI_CONFORMANCE: EbiCommand = EbiCommand::Group {
    name_short: "conf",
    name_long: Some("conformance"),
    explanation_short: "Check the conformance of two stochastic languages.",
    explanation_long: None,
    children: &[
        &EBI_CONFORMANCE_CHI_SQUARED,
        &EBI_CONFORMANCE_CHI_SQUARED_SAMPLE,
        &EBI_CONFORMANCE_EARTH_MOVERS,
        &EBI_CONFORMANCE_EARTH_MOVERS_SAMPLE,
        &EBI_CONFORMANCE_ENTROPIC_RELEVANCE,
        &EBI_CONFORMANCE_GAIN_PRECISION,
        &EBI_CONFORMANCE_GAIN_RECALL,
        &EBI_CONFORMANCE_HELLINGER,
        &EBI_CONFORMANCE_HELLINGER_SAMPLE,
        &EBI_CONFORMANCE_JSSC,
        &EBI_CONFORMANCE_JSSC_SAMPLE,
        &EBI_CONFORMANCE_MARKOVIAN,
        &EBI_CONFORMANCE_UEMSC,
        &EBI_CONFORMANCE_UEMSC_SAMPLE,
    ],
};

pub const EBI_CONFORMANCE_UEMSC: EbiCommand = EbiCommand::Command {
    name_short: "uemsc",
    name_long: Some("unit-earth-movers"),
    explanation_short: "Compute unit-earth movers' stochastic conformance.",
    explanation_long: Some(
        "Compute unit-earth movers' stochastic conformance, which is 1 - the total variation distance.",
    ),
    latex_link: Some(
        r"Unit earth movers' stochastic conformance is computed as follows:
        
        \begin{definition}[Unit earth movers' stochastic conformance~\cite{DBLP:conf/bpm/LeemansSA19}]
            \label{def:uemsc}
                Let $L$ be a finite stochastic language and let $M$ be a queriable stochastic langauge.
                Then, the \emph{unit earth movers' stochastic conformance ($\text{uemsc}$) of $L$ and $M$} is defined as follows: 
                \begin{align*}
                    \text{uemsc}(L, M) ={}& 1 - \sum_{t \in L} \max(L(t) - M(t),0)
                \end{align*}       
            \end{definition}",
    ),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Trait(EbiTrait::QueriableStochasticLanguage)],
    ],
    input_names: &["SLANG", "MODEL"],
    input_helps: &[
        "A finite stochastic language (log) to compare.",
        "A queriable stochastic language (model) to compare.",
    ],
    execute: |mut inputs, _| {
        let log: Box<dyn EbiTraitFiniteStochasticLanguage> = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>(
        )?;
        let model = inputs
            .remove(0)
            .to_type::<dyn EbiTraitQueriableStochasticLanguage>()?;
        let uemsc = log
            .unit_earth_movers_stochastic_conformance(model)
            .context("cannot compute uEMSC")?;
        Ok(EbiOutput::Fraction(uemsc))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_UEMSC_SAMPLE: EbiCommand = EbiCommand::Command {
    name_short: "uemsc-sample",
    name_long: Some("unit-earth-movers-sample"),
    explanation_short: "Compute unit-earth movers' stochastic conformance, which is 1 - the total variation distance, if both inputs need to be sampled.",
    explanation_long: Some(
        "Compute unit-earth movers' stochastic conformance, which is 1 - the total variation distance, if both inputs need to be sampled. If one input is a log or a finite stochastic language, then use `uemsc`.",
    ),
    latex_link: Some("see \\cref{def:uemsc}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        SAMPLED_OBJECT_INPUTS,
        SAMPLED_OBJECT_INPUTS,
        &[&EbiInputType::Usize(Some(1), None, None)],
    ],
    input_names: &["FILE_1", "FILE_2", "NUMBER_OF_TRACES"],
    input_helps: &[
        "A sampleable stochastic language to compare.",
        "A sampleable stochastic language to compare.",
        "Number of traces to sample.",
    ],
    execute: |mut inputs, _| {
        let object1 = inputs.remove(0);
        let object2 = inputs.remove(0);
        let number_of_traces = inputs.remove(0).to_type::<usize>()?;

        let lang1 = Box::new(ebi_command_sample::get_sampled_object(
            object1,
            *number_of_traces,
        )?);
        let lang2 = Box::new(ebi_command_sample::get_sampled_object(
            object2,
            *number_of_traces,
        )?);

        let lang1: Box<dyn EbiTraitFiniteStochasticLanguage> = lang1;
        Ok(EbiOutput::Fraction(
            lang1
                .unit_earth_movers_stochastic_conformance(lang2)
                .context("Compute JSSC by sampling.")?,
        ))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_ENTROPIC_RELEVANCE: EbiCommand = EbiCommand::Command {
    name_short: "er",
    name_long: Some("entropic-relevance"),
    explanation_short: "Compute entropic relevance (uniform).",
    explanation_long: None,
    latex_link: Some(
        r"Entropic relevance is computed as follows:
        
        \begin{definition}[Entropic Relevance~\cite{DBLP:journals/is/AlkhammashPMG22}]
            \label{def:ER}
                Let $L$ be a finite stochastic language and let $M$ be a queriable stochastic langauge.
                Let $\Lambda$ be the set of all activities appearing in the traces of $L$.
                Then, the \emph{entropic relevance ($\entrel$) of $M$ to $L$} is defined as follows: 
                \begin{align*}
                    \entrel(L, M) ={}& H_0\left(\sum_{\sigma \in \bar{L},\, M(\sigma)>0}{L(\sigma)}\right) + 
                    \sum_{\sigma \in \bar{L}}L(\sigma) J(\sigma, M)\\
                    J(\sigma, M) ={}& \begin{cases}
                    -\log_2 M(\sigma) & M(\sigma) > 0\\
                    (1+|\sigma|) \log_2 (1 + |\Lambda|)) & \text{otherwise}
                    \end{cases}\\
                    H_0(x) ={}& -x \log_2{x} - (1-x) \log_2{(1-x)} \text{ with } H_0(0) = H_0(1) = 0 &\\
                \end{align*}       
            \end{definition}",
    ),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Trait(EbiTrait::QueriableStochasticLanguage)],
    ],
    input_names: &["SLANG", "MODEL"],
    input_helps: &[
        "A finite stochastic language (log) to compare.",
        "A queriable stochastic language (model) to compare.",
    ],
    execute: |mut inputs, _| {
        let log = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let model = inputs
            .remove(0)
            .to_type::<dyn EbiTraitQueriableStochasticLanguage>()?;
        Ok(EbiOutput::LogPolynomial(
            log.entropic_relevance(model).context("cannot compute ER")?,
        ))
    },
    output_type: &EbiOutputType::LogPolynomial,
};

pub const EBI_CONFORMANCE_JSSC: EbiCommand = EbiCommand::Command {
    name_short: "jssc",
    name_long: Some("jensen-shannon"),
    explanation_short: "Compute Jensen-Shannon stochastic conformance, which is 1 - the Jensen-Shannon distance.",
    explanation_long: None,
    latex_link: Some(
        r"Compute Jensen-Shannon stochastic conformance is computed as follows:
        
        \begin{definition}[Jensen-Shannon stochastic conformance~\cite{DBLP:conf/icpm/LiLP24}]
            \label{def:jssc}
                Let $L$ be a finite stochastic language, let $M$ be a queriable stochastic langauge, and let $\Sigma$ be their combined alphabet.
                Then, the \emph{Jensen-Shannon stochastic conformance ($\text{jssc}$) of $L$ and $M$} is defined as follows: 
                \begin{align*}
                    \text{jssc}(L, M) ={}& 1 - \sqrt{\frac{\sum_{\sigma \in \Sigma^*} L(\sigma) \log_2 \frac{2 L(\sigma)}{L(\sigma) + M(\sigma)} + M(\sigma) \log_2 \frac{2M(\sigma)}{L(\sigma)+ M(\sigma)}}{2}}
                \end{align*}       
            \end{definition}",
    ),
    cli_command: None,
    exact_arithmetic: false,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[
            &EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage),
            &EbiInputType::Trait(EbiTrait::QueriableStochasticLanguage),
        ],
    ],
    input_names: &["SLANG", "MODEL"],
    input_helps: &[
        "A finite stochastic language to compare.",
        "A queriable stochastic language to compare.",
    ],
    execute: |mut inputs, _| {
        let event_log = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;

        match inputs.remove(0) {
            EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(slang), _) => Ok(
                EbiOutput::RootLogDiv(event_log.jssc_log2log(slang).context("Compute JSSC.")?),
            ),
            EbiInput::Trait(EbiTraitObject::QueriableStochasticLanguage(slang), _) => Ok(
                EbiOutput::RootLogDiv(event_log.jssc_log2model(slang).context("Compute JSSC.")?),
            ),
            _ => Err(anyhow!("wrong input given")),
        }
    },
    output_type: &EbiOutputType::RootLogDiv,
};

pub const EBI_CONFORMANCE_JSSC_SAMPLE: EbiCommand = EbiCommand::Command {
    name_short: "jssc-sample",
    name_long: Some("jensen-shannon-sample"),
    explanation_short: "Compute Jensen-Shannon stochastic conformance, which is 1 - the Jensen-Shannon distance, if both inputs need to be sampled.",
    explanation_long: Some(
        "Compute Jensen-Shannon stochastic conformance, which is 1 - the Jensen-Shannon distance, if both inputs need to be sampled. If one input is a log or a finite stochastic language, then use `jssc`.",
    ),
    latex_link: Some("see \\cref{def:jssc}"),
    cli_command: None,
    exact_arithmetic: false,
    input_types: &[
        SAMPLED_OBJECT_INPUTS,
        SAMPLED_OBJECT_INPUTS,
        &[&EbiInputType::Usize(Some(1), None, None)],
    ],
    input_names: &["FILE_1", "FILE_2", "NUMBER_OF_TRACES"],
    input_helps: &[
        "A sampleable stochastic language to compare.",
        "A sampleable stochastic language to compare.",
        "Number of traces to sample.",
    ],
    execute: |mut inputs, _| {
        let object1 = inputs.remove(0);
        let object2 = inputs.remove(0);
        let number_of_traces = inputs.remove(0).to_type::<usize>()?;

        let lang1 = Box::new(ebi_command_sample::get_sampled_object(
            object1,
            *number_of_traces,
        )?);
        let lang2 = Box::new(ebi_command_sample::get_sampled_object(
            object2,
            *number_of_traces,
        )?);

        let lang1: Box<dyn EbiTraitFiniteStochasticLanguage> = lang1;
        Ok(EbiOutput::RootLogDiv(
            lang1
                .jssc_log2log(lang2)
                .context("Compute JSSC by sampling.")?,
        ))
    },
    output_type: &EbiOutputType::RootLogDiv,
};

pub const EBI_CONFORMANCE_EARTH_MOVERS: EbiCommand = EbiCommand::Command {
    name_short: "emsc",
    name_long: Some("earth-movers"),
    explanation_short: "Compute Earth mover's stochastic conformance.",
    explanation_long: Some(
        "Compute Earth mover's stochastic conformance, which is 1 - the Wasserstein distance.",
    ),
    latex_link: Some(
        r"Earth movers' stochastic conformance is computed as follows:
        
        \begin{definition}[Earth movers' stochastic conformance~\cite{DBLP:journals/is/LeemansABP21}]
            \label{def:emsc}
                Let $L$ and $L'$ be a finite stochastic languages.
                Then, the \emph{Earth movers' stochastic conformance ($\text{emsc}$) of $L$ and $L'$} is defined as follows, with reallocation matrix $R$ and normalised Levenshtein distance $\delta$:
                \begin{align*}
                    \text{emsc}(L, L') ={}& 1 - \min_{R} \sum_{t \in L}\sum_{t' \in L'} R(t, t') \delta(t,t')\\
                    \text{ such that } & \forall_{t\in L} L(t) \leq \sum_{t' \in L'} R(t, t') \land{}\\
                    & \forall_{t' \in L'} L'(t') \leq \sum_{t \in L} R(t, t') \land{}\\
                    & \sum_{t \in L} \sum_{t' \in L'} R(t,t') = 1
                \end{align*}       
            \end{definition}
            
        In case one or both of the inputs are partially ordered, EMSC is computed with a corresponding distance function.",
    ),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[
            &EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage),
            &EbiInputType::Object(EbiObjectType::FiniteStochasticPartiallyOrderedLanguage),
        ],
        &[
            &EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage),
            &EbiInputType::Object(EbiObjectType::FiniteStochasticPartiallyOrderedLanguage),
        ],
    ],
    input_names: &["SLANG", "SLANG"],
    input_helps: &[
        "A finite stochastic language to compare (may be partially ordered).",
        "A finite stochastic language to compare (may be partially ordered).",
    ],
    execute: |mut inputs, _| {
        let lang_a = inputs.remove(0);
        let lang_b = inputs.remove(0);

        // .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;

        match (lang_a, lang_b) {
            (
                EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(mut lang_a), _),
                EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(mut lang_b), _),
            ) => Ok(EbiOutput::Fraction(
                lang_a
                    .earth_movers_stochastic_conformance(lang_b.as_mut())
                    .with_context(|| anyhow!("Error while computing EMSC."))?,
            )),
            (
                EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(mut lang_a), _),
                EbiInput::Object(
                    EbiObject::FiniteStochasticPartiallyOrderedLanguage(mut lang_b),
                    _,
                ),
            ) => Ok(EbiOutput::Fraction(
                lang_a
                    .partially_ordered_earth_movers_stochastic_conformance(&mut lang_b)
                    .with_context(|| anyhow!("Error while computing partially ordered EMSC."))?,
            )),
            (
                EbiInput::Object(
                    EbiObject::FiniteStochasticPartiallyOrderedLanguage(mut lang_a),
                    _,
                ),
                EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(mut lang_b), _),
            ) => Ok(EbiOutput::Fraction(
                lang_b
                    .partially_ordered_earth_movers_stochastic_conformance(&mut lang_a)
                    .with_context(|| anyhow!("Error while computing partially ordered EMSC."))?,
            )),
            (
                EbiInput::Object(
                    EbiObject::FiniteStochasticPartiallyOrderedLanguage(mut lang_a),
                    _,
                ),
                EbiInput::Object(
                    EbiObject::FiniteStochasticPartiallyOrderedLanguage(mut lang_b),
                    _,
                ),
            ) => Ok(EbiOutput::Fraction(
                lang_a
                    .partially_ordered_earth_movers_stochastic_conformance(&mut lang_b)
                    .with_context(|| anyhow!("Error while computing partially ordered EMSC."))?,
            )),
            _ => Err(anyhow!("Inputs not recognised.")),
        }
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_EARTH_MOVERS_SAMPLE: EbiCommand = EbiCommand::Command {
    name_short: "emsc-sample",
    name_long: Some("earth-movers-sample"),
    explanation_short: "Compute Earth mover's stochastic conformance, which is 1 - the Wasserstein distance, where one or both of the inputs needs to be sampled.",
    explanation_long: Some(
        "Compute Earth mover's stochastic conformance, which is 1 - the Wasserstein distance, where one or both of the inputs needs to be sampled. If both inputs are logs or finite stochastic languages, then use `emsc`.",
    ),
    latex_link: Some("see \\cref{def:emsc}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        SAMPLED_OBJECT_INPUTS,
        SAMPLED_OBJECT_INPUTS,
        &[&EbiInputType::Usize(Some(1), None, None)],
    ],
    input_names: &["FILE_1", "FILE_2", "NUMBER_OF_TRACES"],
    input_helps: &[
        "A stochastic language (log or model) to compare.",
        "A stochastic language (log or model) to compare.",
        "Number of traces to sample.",
    ],
    execute: |mut inputs, _| {
        let object_a = inputs.remove(0);
        let object_b = inputs.remove(0);
        let number_of_traces = inputs.remove(0).to_type::<usize>()?;

        let mut lang_a =
            ebi_command_sample::get_sampled_object_if_necessary(object_a, *number_of_traces)?;
        let mut lang_b =
            ebi_command_sample::get_sampled_object_if_necessary(object_b, *number_of_traces)?;

        // Compute EMSC
        Ok(EbiOutput::Fraction(
            lang_a
                .earth_movers_stochastic_conformance(lang_b.as_mut())
                .context("Compute EMSC.")?,
        ))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_HELLINGER: EbiCommand = EbiCommand::Command {
    name_short: "hsc",
    name_long: Some("hellinger"),
    explanation_short: "Compute Hellinger stochastic conformance.",
    explanation_long: Some(
        "Compute Hellinger stochastic conformance, which is 1 - the Hellinger distance.",
    ),
    latex_link: Some(
        r"Hellinger stochastic conformance is computed as follows:
        
        \begin{definition}[Hellinger stochastic conformance~\cite{hellinger1909neue}]
            \label{def:hsc}
                Let $L$ be a finite stochastic language and let $M$ be a queriable stochastic langauge.
                Then, the \emph{Hellinger stochastic conformance ($\text{hsc}$) of $L$ and $M$} is defined as follows: 
                \begin{align*}
                    \text{hsc}(L, M) ={}& \frac{1}{\sqrt{2}}\sqrt{\sum_{\sigma \in L \cup M}(\sqrt{L(x)}-\sqrt{M(x)})^2}
                \end{align*}       
            \end{definition}",
    ),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Trait(EbiTrait::QueriableStochasticLanguage)],
    ],
    input_names: &["SLANG", "MODEL"],
    input_helps: &[
        "A finite stochastic language (log) to compare.",
        "A queriable stochastic language (model) to compare.",
    ],
    execute: |mut inputs, _| {
        let log = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let model = inputs
            .remove(0)
            .to_type::<dyn EbiTraitQueriableStochasticLanguage>()?;
        let hsc = log
            .hellinger_stochastic_conformance(model)
            .context("cannot compute HSC")?;
        Ok(EbiOutput::Fraction(hsc))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_HELLINGER_SAMPLE: EbiCommand = EbiCommand::Command {
    name_short: "hsc-sample",
    name_long: Some("hellinger-sample"),
    explanation_short: "Compute Hellinger stochastic conformance, which is 1 - the Hellinger distance, if both inputs need to be sampled.",
    explanation_long: Some(
        "Compute Hellinger stochastic conformance, which is 1 - the Hellinger distance, if both inputs need to be sampled. If one input is a log or a finite stochastic language, then use `hsc`.",
    ),
    latex_link: Some("see \\cref{def:hsc}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        SAMPLED_OBJECT_INPUTS,
        SAMPLED_OBJECT_INPUTS,
        &[&EbiInputType::Usize(Some(1), None, None)],
    ],
    input_names: &["FILE_1", "FILE_2", "NUMBER_OF_TRACES"],
    input_helps: &[
        "A sampleable stochastic language to compare.",
        "A sampleable stochastic language to compare.",
        "Number of traces to sample.",
    ],
    execute: |mut inputs, _| {
        let object1 = inputs.remove(0);
        let object2 = inputs.remove(0);
        let number_of_traces = inputs.remove(0).to_type::<usize>()?;

        let lang1 = Box::new(ebi_command_sample::get_sampled_object(
            object1,
            *number_of_traces,
        )?);
        let lang2 = Box::new(ebi_command_sample::get_sampled_object(
            object2,
            *number_of_traces,
        )?);

        let lang1: Box<dyn EbiTraitFiniteStochasticLanguage> = lang1;
        Ok(EbiOutput::Fraction(
            lang1
                .hellinger_stochastic_conformance(lang2)
                .context("Compute HSC by sampling.")?,
        ))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_CHI_SQUARED: EbiCommand = EbiCommand::Command {
    name_short: "cssc",
    name_long: Some("chi-squared"),
    explanation_short: "Compute Chi-Square stochastic conformance.",
    explanation_long: Some("Compute chi-square stochastic conformance."),
    latex_link: Some(
        r"Chi-squared stochastic conformance is computed as follows:
        
        \begin{definition}[Chi-squared stochastic conformance~\cite{DBLP:conf/eccv/PeleW10}]
            \label{def:cssc}
                Let $L$ be a finite stochastic language and let $M$ be a queriable stochastic langauge.
                Then, the \emph{chi-squared stochastic conformance ($\text{cssc}$) of $L$ and $M$} is defined as follows: 
                \begin{align*}
                    \text{cssc}(L, M) ={}& 1 - \frac{1}{2} \sum_{\sigma \in L \cup M} \frac{(L(\sigma)-M(\sigma))^2}{L(\sigma)+M(\sigma)}
                \end{align*}       
            \end{definition}",
    ),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Trait(EbiTrait::QueriableStochasticLanguage)],
    ],
    input_names: &["SLANG", "MODEL"],
    input_helps: &[
        "A finite stochastic language (log) to compare.",
        "A queriable stochastic language (model) to compare.",
    ],
    execute: |mut inputs, _| {
        let log = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let model = inputs
            .remove(0)
            .to_type::<dyn EbiTraitQueriableStochasticLanguage>()?;
        let cssc = log
            .chi_square_stochastic_conformance(model)
            .context("cannot compute CSSC")?;
        Ok(EbiOutput::Fraction(cssc))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_CHI_SQUARED_SAMPLE: EbiCommand = EbiCommand::Command {
    name_short: "cssc-sample",
    name_long: Some("chi-squared-sample"),
    explanation_short: "Compute chi-squared stochastic conformance, if both inputs need to be sampled.",
    explanation_long: Some(
        "Compute chi-square stochastic conformance, if both inputs need to be sampled. If one input is a log or a finite stochastic language, then use `cssc`.",
    ),
    latex_link: Some("see \\cref{def:cssc}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        SAMPLED_OBJECT_INPUTS,
        SAMPLED_OBJECT_INPUTS,
        &[&EbiInputType::Usize(Some(1), None, None)],
    ],
    input_names: &["FILE_1", "FILE_2", "NUMBER_OF_TRACES"],
    input_helps: &[
        "A sampleable stochastic language to compare.",
        "A sampleable stochastic language to compare.",
        "Number of traces to sample.",
    ],
    execute: |mut inputs, _| {
        let object1 = inputs.remove(0);
        let object2 = inputs.remove(0);
        let number_of_traces = inputs.remove(0).to_type::<usize>()?;

        let lang1 = Box::new(ebi_command_sample::get_sampled_object(
            object1,
            *number_of_traces,
        )?);
        let lang2 = Box::new(ebi_command_sample::get_sampled_object(
            object2,
            *number_of_traces,
        )?);

        let lang1: Box<dyn EbiTraitFiniteStochasticLanguage> = lang1;
        Ok(EbiOutput::Fraction(
            lang1
                .chi_square_stochastic_conformance(lang2)
                .context("Compute CSSC by sampling.")?,
        ))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_MARKOVIAN: EbiCommand = EbiCommand::Command {
    name_short: "ma",
    name_long: Some("markovian"),
    explanation_short: "Compute the conformance between two stochastic languages using a stochastic Markovian abstraction.",
    explanation_long: Some(
        "Compute the conformance between two stochastic languages using a stochastic Markovian abstraction, which represents languages based on the expected frequency of subtraces to handle partially matching traces.",
    ),
    latex_link: Some("\\cite{DBLP:journals/procsci/RochaLA25}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[
            &EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage),
            &EbiInputType::Object(EbiObjectType::StochasticNondeterministicFiniteAutomaton),
            &EbiInputType::Object(EbiObjectType::StochasticLabelledPetriNet),
        ],
        &[
            &EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage),
            &EbiInputType::Object(EbiObjectType::StochasticNondeterministicFiniteAutomaton),
            &EbiInputType::Object(EbiObjectType::StochasticLabelledPetriNet),
        ],
        &[&EbiInputType::Usize(Some(1), None, None)],
        &[&EbiInputTypeEnum!(DistanceMeasure)],
    ],
    input_names: &["FILE_1", "FILE_2", "K_ORDER", "MEASURE"],
    input_helps: &[
        "A finite stochastic language or a stochastic labelled Petri net (log) to compare. A model must be livelock-free and bounded.",
        "A finite stochastic language or a stochastic labelled Petri net (model) to compare. A model must be livelock-free and bounded.",
        "The order of the Markovian abstraction (length of subtraces).",
        "The stochastic conformance measure to be applied to the abstractions.",
    ],
    execute: |mut inputs, _| {
        let mut lang1 = inputs.remove(0);
        let mut lang2 = inputs.remove(0);
        let order = inputs.remove(0).to_type::<usize>()?;
        let measure = inputs.remove(0).to_type::<DistanceMeasure>()?;

        //read abstractions
        let abstraction1 = match &mut lang1 {
            EbiInput::Object(EbiObject::StochasticNondeterministicFiniteAutomaton(snfa), _) => {
                snfa.abstract_markovian(*order)
            }
            EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(slang), _) => {
                slang.abstract_markovian(*order)
            }
            EbiInput::Object(EbiObject::StochasticLabelledPetriNet(slpn), _) => {
                slpn.abstract_markovian(*order)
            }
            _ => {
                return Err(anyhow!(
                    "Unsupported object {:?} provided.",
                    lang1.get_type()
                ));
            }
        }?;
        let abstraction2 = match &mut lang2 {
            EbiInput::Object(EbiObject::StochasticNondeterministicFiniteAutomaton(snfa), _) => {
                snfa.abstract_markovian(*order)
            }
            EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(slang), _) => {
                slang.abstract_markovian(*order)
            }
            EbiInput::Object(EbiObject::StochasticLabelledPetriNet(slpn), _) => {
                slpn.abstract_markovian(*order)
            }
            _ => {
                return Err(anyhow!(
                    "Unsupported object {:?} provided.",
                    lang2.get_type()
                ));
            }
        }?;

        let result = abstraction1.markovian_conformance(abstraction2, *measure)?;

        Ok(EbiOutput::Fraction(result))
    },
    output_type: &EbiOutputType::Fraction,
};

pub const EBI_CONFORMANCE_GAIN_PRECISION: EbiCommand = EbiCommand::Command {
    name_short: "gp",
    name_long: Some("gain-precision"),
    explanation_short: "Compute gain-based precision.",
    explanation_long: Some(
        "Compute gain-based precision for a finite stochastic language and an SDFA. If the SDFA supports less than two traces, set lambda to a small non-zero value.",
    ),
    latex_link: Some("\\cite{DBLP:journals/is/LeemansP23}"),
    cli_command: None,
    exact_arithmetic: false,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(
            EbiObjectType::StochasticDeterministicFiniteAutomaton,
        )],
        &[&EbiInputType::Fraction(
            Some(ConstFraction::zero()),
            Some(ConstFraction::one()),
            Some(ConstFraction::zero()),
        )],
    ],
    input_names: &["SLANG", "MODEL", "LAMBDA"],
    input_helps: &[
        "A finite stochastic language to compare.",
        "A stochastic deterministic finite automaton (SDFA) to compare.",
        "The smoothing parameter. If the SDFA supports less than two traces, set this to a small value.",
    ],
    execute: |mut inputs, _| {
        let slang = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let sdfa = *inputs
            .remove(0)
            .to_type::<StochasticDeterministicFiniteAutomaton>()?;
        let lambda = inputs.remove(0).to_type::<Fraction>()?;

        let precision = sdfa
            .potential_gain_precision(&slang, &lambda)
            .context("Cannot compute gain precision.")?;

        Ok(EbiOutput::LogPolynomial(precision))
    },
    output_type: &EbiOutputType::LogPolynomial,
};

pub const EBI_CONFORMANCE_GAIN_RECALL: EbiCommand = EbiCommand::Command {
    name_short: "gr",
    name_long: Some("gain-recall"),
    explanation_short: "Compute gain-based recall.",
    explanation_long: Some(
        "Compute gain-based recall for a finite stochastic language and an SDFA. If the SDFA supports less than two traces, set lambda to a small non-zero value.",
    ),
    latex_link: Some("\\cite{DBLP:journals/is/LeemansP23}"),
    cli_command: None,
    exact_arithmetic: false,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(
            EbiObjectType::StochasticDeterministicFiniteAutomaton,
        )],
        &[&EbiInputType::Fraction(
            Some(ConstFraction::zero()),
            Some(ConstFraction::one()),
            Some(ConstFraction::zero()),
        )],
    ],
    input_names: &["SLANG", "MODEL", "LAMBDA"],
    input_helps: &[
        "A finite stochastic language to compare.",
        "A stochastic deterministic finite automaton (SDFA) to compare.",
        "The smoothing parameter. If the SDFA supports less than two traces, set this to a small value.",
    ],
    execute: |mut inputs, _| {
        let slang = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let sdfa = *inputs
            .remove(0)
            .to_type::<StochasticDeterministicFiniteAutomaton>()?;
        let lambda = inputs.remove(0).to_type::<Fraction>()?;

        let recall = sdfa
            .potential_gain_recall(&slang, &lambda)
            .context("Cannot compute gain recall.")?;

        Ok(EbiOutput::LogPolynomial(recall))
    },
    output_type: &EbiOutputType::LogPolynomial,
};

#[cfg(test)]
pub mod tests {
    use crate::{
        ebi_traits::ebi_trait_finite_stochastic_language::EbiTraitFiniteStochasticLanguage,
        techniques::earth_movers_stochastic_conformance::EarthMoversStochasticConformance,
    };
    use ebi_objects::FiniteStochasticLanguage;
    use std::fs;

    #[test]
    fn emsc_empty_traces() {
        let fin1 = fs::read_to_string("testfiles/empty_trace.slang").unwrap();
        let mut object_a: Box<dyn EbiTraitFiniteStochasticLanguage> =
            Box::new(fin1.parse::<FiniteStochasticLanguage>().unwrap());

        let fin2 = fs::read_to_string("testfiles/empty_trace.slang").unwrap();
        let mut object_b: Box<dyn EbiTraitFiniteStochasticLanguage> =
            Box::new(fin2.parse::<FiniteStochasticLanguage>().unwrap());

        // Compute EMSC
        object_a
            .earth_movers_stochastic_conformance(object_b.as_mut())
            .unwrap();
    }
}
