use crate::{
    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,
    techniques::{
        alignment_stochastic_miner::AlignmentMiner,
        directly_follows_model_miner::DirectlyFollowsModelMinerFiltering,
        occurrences_stochastic_miner::{
            OccurrencesStochasticMinerBPMN, OccurrencesStochasticMinerLPN,
            OccurrencesStochasticMinerTree,
        },
        random_stochastic_miner::{RandomMinerSBPMN, RandomMinerSTREE},
        uniform_stochastic_miner::{
            UniformStochasticBusinessProcessModelAndNotation, UniformStochasticMinerLPN,
            UniformStochasticMinerTree,
        },
    },
};
use ebi_objects::{
    BusinessProcessModelAndNotation, EbiObject, EbiObjectType, LabelledPetriNet, ProcessTree,
    anyhow::{Context, anyhow},
    ebi_arithmetic::{ConstFraction, Fraction, Random},
};

pub const EBI_DISCOVER: EbiCommand = EbiCommand::Group {
    name_short: "disc",
    name_long: Some("discover"),
    explanation_short: "Discover a stochastic process model.",
    explanation_long: None,
    children: &[
        &EBI_DISCOVER_ALIGNMENTS,
        &EBI_DISCOVER_DIRECTLY_FOLLOWS,
        &EBI_DISCOVER_OCCURRENCE,
        &EBI_DISCOVER_RANDOM,
        &EBI_DISCOVER_UNIFORM,
    ],
};

pub const EBI_DISCOVER_ALIGNMENTS: EbiCommand = EbiCommand::Group {
    name_short: "ali",
    name_long: Some("alignments"),
    explanation_short: "Give each transition a weight that matches the aligned occurrences of its label.",
    explanation_long: Some(
        "Give each transition a weight that matches the aligned occurrences of its label. The model must be livelock-free.",
    ),
    children: &[&EBI_DISCOVER_ALIGNMENTS_BPMN, &EBI_DISCOVER_ALIGNMENTS_SLPN],
};

pub const EBI_DISCOVER_ALIGNMENTS_BPMN: EbiCommand = EbiCommand::Command {
    name_short: "sbpmn",
    name_long: Some("stochastic-business-process-model-and-notation"),
    explanation_short: "Give each transition a weight that matches the aligned occurrences of its label.",
    explanation_long: Some(
        "Give each transition a weight that matches the aligned occurrences of its label. The model must be livelock-free.",
    ),
    latex_link: Some("~\\cite{DBLP:conf/icpm/BurkeLW20}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(
            EbiObjectType::BusinessProcessModelAndNotation,
        )],
    ],
    input_names: &["SLANG", "BPMN"],
    input_helps: &[
        "A finite stochastic language (log) to get the occurrences from.",
        "A business process model and notation with the control flow.",
    ],
    execute: |mut inputs, _| {
        let language = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let bpmn = inputs
            .remove(0)
            .to_type::<BusinessProcessModelAndNotation>()?;
        Ok(EbiOutput::Object(
            EbiObject::StochasticBusinessProcessModelAndNotation(
                bpmn.mine_stochastic_alignment(language)
                    .with_context(|| anyhow!("Discovering using alignments."))?,
            ),
        ))
    },
    output_type: &EbiOutputType::ObjectType(
        EbiObjectType::StochasticBusinessProcessModelAndNotation,
    ),
};

pub const EBI_DISCOVER_ALIGNMENTS_SLPN: EbiCommand = EbiCommand::Command {
    name_short: "slpn",
    name_long: Some("stochastic-labelled-Petri-nets"),
    explanation_short: "Give each transition a weight that matches the aligned occurrences of its label.",
    explanation_long: Some(
        "Give each transition a weight that matches the aligned occurrences of its label. The model must be livelock-free.",
    ),
    latex_link: Some("~\\cite{DBLP:conf/icpm/BurkeLW20}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(EbiObjectType::LabelledPetriNet)],
    ],
    input_names: &["SLANG", "LPN"],
    input_helps: &[
        "A finite stochastic language (log) to get the occurrences from.",
        "A labelled Petri net with the control flow.",
    ],
    execute: |mut inputs, _| {
        let language = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let lpn = inputs.remove(0).to_type::<LabelledPetriNet>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticLabelledPetriNet(
            lpn.mine_stochastic_alignment(language)?,
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticLabelledPetriNet),
};

pub const EBI_DISCOVER_DIRECTLY_FOLLOWS: EbiCommand = EbiCommand::Command {
    name_short: "dfg",
    name_long: Some("directly-follows-graph"),
    explanation_short: "Discover a directly follows graph.",
    explanation_long: None,
    latex_link: Some("~\\cite{DBLP:conf/icpm/LeemansPW19}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[
            &EbiInputType::Trait(EbiTrait::EventLog),
            &EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage),
        ],
        &[&EbiInputType::Fraction(
            Some(ConstFraction::zero()),
            Some(ConstFraction::one()),
            Some(ConstFraction::one()),
        )],
    ],
    input_names: &["SLANG", "MIN_FITNESS"],
    input_helps: &[
        "A finite stochastic language.",
        "The minimum fraction of traces that should fit the resulting model.",
    ],
    execute: |mut inputs, _| {
        let lang = inputs.remove(0);
        let minimum_fitness = inputs
            .remove(0)
            .to_type::<Fraction>()
            .with_context(|| "reading minimum fitness")?;
        match lang {
            EbiInput::Trait(EbiTraitObject::EventLog(mut log), _) => {
                Ok(EbiOutput::Object(EbiObject::DirectlyFollowsGraph(
                    log.mine_directly_follows_model_filtering(&minimum_fitness)?,
                )))
            }
            EbiInput::Trait(EbiTraitObject::FiniteStochasticLanguage(mut slang), _) => {
                Ok(EbiOutput::Object(EbiObject::DirectlyFollowsGraph(
                    slang.mine_directly_follows_model_filtering(&minimum_fitness)?,
                )))
            }
            _ => Err(anyhow!(
                "can only apply directly follows model miner to event log or finite stochastic language"
            )),
        }
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::DirectlyFollowsGraph),
};

pub const EBI_DISCOVER_OCCURRENCE: EbiCommand = EbiCommand::Group {
    name_short: "occ",
    name_long: Some("occurrence"),
    explanation_short: "Give each transition a weight that matches the occurrences of its label; silent transitions get a weight of 1.",
    explanation_long: None,
    children: &[
        &EBI_DISCOVER_OCCURRENCE_SBPMN,
        &EBI_DISCOVER_OCCURRENCE_SLPN,
        &EBI_DISCOVER_OCCURRENCE_SPTREE,
    ],
};

pub const EBI_DISCOVER_OCCURRENCE_SBPMN: EbiCommand = EbiCommand::Command {
    name_short: "sbpmn",
    name_long: Some("stochastic-business-process-model-and-notation"),
    explanation_short: "Give each sequence flow to a task a weight that matches the occurrences of its label; other sequence flows get a weight of 1.",
    explanation_long: None,
    latex_link: Some("~\\cite{DBLP:conf/icpm/BurkeLW20}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(
            EbiObjectType::BusinessProcessModelAndNotation,
        )],
    ],
    input_names: &["SLANG", "BPMN"],
    input_helps: &[
        "A finite stochastic language (log) to get the occurrences from.",
        "A business process model and notation model with the control flow.",
    ],
    execute: |mut inputs, _| {
        let language = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let bpmn = inputs
            .remove(0)
            .to_type::<BusinessProcessModelAndNotation>()?;
        Ok(EbiOutput::Object(
            EbiObject::StochasticBusinessProcessModelAndNotation(
                bpmn.mine_occurrences_stochastic_bpmn(language)?,
            ),
        ))
    },
    output_type: &EbiOutputType::ObjectType(
        EbiObjectType::StochasticBusinessProcessModelAndNotation,
    ),
};

pub const EBI_DISCOVER_OCCURRENCE_SLPN: EbiCommand = EbiCommand::Command {
    name_short: "slpn",
    name_long: Some("stochastic-labelled-Petri-net"),
    explanation_short: "Give each transition a weight that matches the occurrences of its label; silent transitions get a weight of 1.",
    explanation_long: None,
    latex_link: Some("~\\cite{DBLP:conf/icpm/BurkeLW20}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(EbiObjectType::LabelledPetriNet)],
    ],
    input_names: &["SLANG", "LPN"],
    input_helps: &[
        "A finite stochastic language (log) to get the occurrences from.",
        "A labelled Petri net with the control flow.",
    ],
    execute: |mut inputs, _| {
        let language = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let lpn = inputs.remove(0).to_type::<LabelledPetriNet>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticLabelledPetriNet(
            lpn.mine_occurrences_stochastic_lpn(language),
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticLabelledPetriNet),
};

pub const EBI_DISCOVER_OCCURRENCE_SPTREE: EbiCommand = EbiCommand::Command {
    name_short: "sptree",
    name_long: Some("stochastic-process-tree"),
    explanation_short: "Give each leaf a weight that matches the occurrences of its label; silent leaves get a weight of 1.",
    explanation_long: None,
    latex_link: Some("~\\cite{DBLP:conf/icpm/BurkeLW20}"),
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[
        &[&EbiInputType::Trait(EbiTrait::FiniteStochasticLanguage)],
        &[&EbiInputType::Object(EbiObjectType::ProcessTree)],
    ],
    input_names: &["SLANG", "TREE"],
    input_helps: &[
        "A finite stochastic language (log) to get the occurrences from.",
        "A process tree with the control flow.",
    ],
    execute: |mut inputs, _| {
        let language = inputs
            .remove(0)
            .to_type::<dyn EbiTraitFiniteStochasticLanguage>()?;
        let lpn = inputs.remove(0).to_type::<ProcessTree>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticProcessTree(
            lpn.mine_occurrences_stochastic_tree(language),
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticProcessTree),
};

pub const EBI_DISCOVER_RANDOM: EbiCommand = EbiCommand::Group {
    name_short: "rnd",
    name_long: Some("random"),
    explanation_short: "Give each transition a random weight between 0 (exclusive) and 1 (inclusive).",
    explanation_long: None,
    children: &[
        &EBI_DISCOVER_RANDOM_SBPMN,
        &EBI_DISCOVER_RANDOM_SLPN,
        &EBI_DISCOVER_RANDOM_SPTREE,
    ],
};

pub const EBI_DISCOVER_RANDOM_SBPMN: EbiCommand = EbiCommand::Command {
    name_short: "sbpmn",
    name_long: Some("stochastic-business-process-model-and-notation"),
    explanation_short: "Give each sequence flow a random weight between 0 (exclusive) and 1 (inclusive).",
    explanation_long: None,
    latex_link: None,
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[&[&EbiInputType::Object(
        EbiObjectType::BusinessProcessModelAndNotation,
    )]],
    input_names: &["BPMN"],
    input_helps: &["A business process model and notation model with the control flow."],
    execute: |mut inputs, _| {
        let bpmn = inputs
            .remove(0)
            .to_type::<BusinessProcessModelAndNotation>()?;
        Ok(EbiOutput::Object(
            EbiObject::StochasticBusinessProcessModelAndNotation(
                bpmn.mine_random_stochastic_business_process_model_and_notation(
                    Fraction::random_seed(),
                )?,
            ),
        ))
    },
    output_type: &EbiOutputType::ObjectType(
        EbiObjectType::StochasticBusinessProcessModelAndNotation,
    ),
};

pub const EBI_DISCOVER_RANDOM_SLPN: EbiCommand = EbiCommand::Command {
    name_short: "slpn",
    name_long: Some("stochastic-labelled-Petri-net"),
    explanation_short: "Give each transition a random weight between 0 (exclusive) and 1 (inclusive).",
    explanation_long: None,
    latex_link: None,
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[&[&EbiInputType::Object(EbiObjectType::LabelledPetriNet)]],
    input_names: &["LPN"],
    input_helps: &["A labelled Petri net."],
    execute: |mut inputs, _| {
        let lpn = inputs.remove(0).to_type::<LabelledPetriNet>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticLabelledPetriNet(
            lpn.mine_uniform_stochastic_lpn(),
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticLabelledPetriNet),
};

pub const EBI_DISCOVER_RANDOM_SPTREE: EbiCommand = EbiCommand::Command {
    name_short: "sptree",
    name_long: Some("stochastic-process-tree"),
    explanation_short: "Give each leaf a random weight between 0 (exclusive) and 1 (inclusive).",
    explanation_long: None,
    latex_link: None,
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[&[&EbiInputType::Object(EbiObjectType::ProcessTree)]],
    input_names: &["TREE"],
    input_helps: &["A process tree."],
    execute: |mut inputs, _| {
        let tree = inputs.remove(0).to_type::<ProcessTree>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticProcessTree(
            tree.mine_random_stochastic_process_tree(Fraction::random_seed()),
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticProcessTree),
};

pub const EBI_DISCOVER_UNIFORM: EbiCommand = EbiCommand::Group {
    name_short: "uni",
    name_long: Some("uniform"),
    explanation_short: "Give each transition a weight of 1.",
    explanation_long: None,
    children: &[
        &EBI_DISCOVER_UNIFORM_SBPMN,
        &EBI_DISCOVER_UNIFORM_SLPN,
        &EBI_DISCOVER_UNIFORM_SPTREE,
    ],
};

pub const EBI_DISCOVER_UNIFORM_SLPN: EbiCommand = EbiCommand::Command {
    name_short: "slpn",
    name_long: Some("stochastic-labelled-Petri-net"),
    explanation_short: "Give each transition a weight of 1.",
    explanation_long: None,
    latex_link: None,
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[&[&EbiInputType::Object(EbiObjectType::LabelledPetriNet)]],
    input_names: &["LPN"],
    input_helps: &["A labelled Petri net."],
    execute: |mut inputs, _| {
        let lpn = inputs.remove(0).to_type::<LabelledPetriNet>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticLabelledPetriNet(
            lpn.mine_uniform_stochastic_lpn(),
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticLabelledPetriNet),
};

pub const EBI_DISCOVER_UNIFORM_SPTREE: EbiCommand = EbiCommand::Command {
    name_short: "sptree",
    name_long: Some("stochastic-process-tree"),
    explanation_short: "Give each leaf a weight of 1 in a process tree.",
    explanation_long: None,
    latex_link: None,
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[&[&EbiInputType::Object(EbiObjectType::ProcessTree)]],
    input_names: &["TREE"],
    input_helps: &["A process tree."],
    execute: |mut inputs, _| {
        let tree = inputs.remove(0).to_type::<ProcessTree>()?;
        Ok(EbiOutput::Object(EbiObject::StochasticProcessTree(
            tree.mine_uniform_stochastic_tree(),
        )))
    },
    output_type: &EbiOutputType::ObjectType(EbiObjectType::StochasticProcessTree),
};

pub const EBI_DISCOVER_UNIFORM_SBPMN: EbiCommand = EbiCommand::Command {
    name_short: "sbpmn",
    name_long: Some("stochastic-business-process-model-and-notation"),
    explanation_short: "Give each sequence flow a weight of 1.",
    explanation_long: None,
    latex_link: None,
    cli_command: None,
    exact_arithmetic: true,
    input_types: &[&[&EbiInputType::Object(
        EbiObjectType::BusinessProcessModelAndNotation,
    )]],
    input_names: &["BPMN"],
    input_helps: &["A business-process-model-and-notation model."],
    execute: |mut inputs, _| {
        let bpmn = inputs
            .remove(0)
            .to_type::<BusinessProcessModelAndNotation>()?;
        Ok(EbiOutput::Object(
            EbiObject::StochasticBusinessProcessModelAndNotation(
                bpmn.mine_uniform_stochastic_business_process_model_and_notation()?,
            ),
        ))
    },
    output_type: &EbiOutputType::ObjectType(
        EbiObjectType::StochasticBusinessProcessModelAndNotation,
    ),
};
