use super::{
    ebi_command::{EBI_COMMANDS, EbiCommand},
    ebi_file_handler::EBI_FILE_HANDLERS,
    ebi_trait::{EbiTrait, FromEbiTraitObject},
};
use crate::{
    ebi_framework::{
        documentation::FILE_HANDLERS_PAGE,
        ebi_file_handler::{EbiFileHandler, get_file_handlers, get_file_handlers_fallible},
        ebi_importer_parameters,
        ebi_trait_object::EbiTraitObject,
    },
    ebi_traits::{
        ebi_trait_activities::EbiTraitActivities, ebi_trait_event_log::EbiTraitEventLog,
        ebi_trait_event_log_event_attributes::EbiTraitEventLogEventAttributes,
        ebi_trait_event_log_trace_attributes::EbiTraitEventLogTraceAttributes,
        ebi_trait_finite_language::EbiTraitFiniteLanguage,
        ebi_trait_finite_stochastic_language::EbiTraitFiniteStochasticLanguage,
        ebi_trait_graphable::EbiTraitGraphable,
        ebi_trait_iterable_language::EbiTraitIterableLanguage,
        ebi_trait_iterable_stochastic_language::EbiTraitIterableStochasticLanguage,
        ebi_trait_queriable_stochastic_language::EbiTraitQueriableStochasticLanguage,
        ebi_trait_semantics::EbiTraitSemantics,
        ebi_trait_stochastic_deterministic_semantics::EbiTraitStochasticDeterministicSemantics,
        ebi_trait_stochastic_partially_ordered_semantics::EbiTraitStochasticPartiallyOrderedSemantics,
        ebi_trait_stochastic_semantics::EbiTraitStochasticSemantics,
    },
    ebi_validate,
    multiple_reader::MultipleReader,
    text::Joiner,
};
use clap::{ArgMatches, builder::ValueParser, value_parser};
use ebi_objects::{
    EbiObject, EbiObjectType,
    anyhow::{Context, Error, Result, anyhow},
    ebi_arithmetic::{ConstFraction, Fraction, parsing::FractionNotParsedYet},
    traits::importable::{ImporterParameter, ImporterParameterValues},
};
use std::{
    collections::{BTreeSet, HashSet},
    fmt::{self, Debug, Display},
    fs::File,
    io::BufRead,
    path::PathBuf,
};
use strum_macros::{EnumIs, EnumIter, IntoStaticStr};

pub enum EbiInput {
    Trait(EbiTraitObject, &'static EbiFileHandler),
    Object(EbiObject, &'static EbiFileHandler),
    String(String, &'static EbiInputType),
    Usize(usize, &'static EbiInputType),
    FileHandler(EbiFileHandler),
    Fraction(Fraction, &'static EbiInputType),
}

impl EbiInput {
    pub fn to_type<T: FromEbiTraitObject + ?Sized>(self) -> Result<Box<T>> {
        FromEbiTraitObject::from_trait_object(self)
    }

    pub fn get_type(&self) -> EbiInputType {
        match self {
            EbiInput::Trait(t, _) => EbiInputType::Trait(t.get_trait()),
            EbiInput::Object(o, _) => EbiInputType::Object(o.get_type()),
            EbiInput::String(_, input_type) => (*input_type).clone(),
            EbiInput::Usize(_, input_type) => (*input_type).clone(),
            EbiInput::FileHandler(_) => EbiInputType::FileHandler,
            EbiInput::Fraction(_, input_type) => (*input_type).clone(),
        }
    }

    pub fn importing_file_handler(&self) -> Option<&'static EbiFileHandler> {
        match self {
            EbiInput::Trait(_, ebi_file_handler) => Some(ebi_file_handler),
            EbiInput::Object(_, ebi_file_handler) => Some(ebi_file_handler),
            EbiInput::String(_, _) => None,
            EbiInput::Usize(_, _) => None,
            EbiInput::FileHandler(_) => None,
            EbiInput::Fraction(_, _) => None,
        }
    }
}

#[macro_export]
macro_rules! EbiInputTypeEnum {
    ($t:ident) => {
        EbiInputType::String(Some($t::VARIANTS), None)
    };
}

#[derive(PartialEq, Eq, EnumIter, Clone, Debug, EnumIs)]
pub enum EbiInputType {
    Trait(EbiTrait),
    Object(EbiObjectType),
    AnyObject,
    FileHandler,

    ///Fields: allowed values, default value.
    /// If the allowed values are limited, consider using an enum annotated with #[derive(EbiInputEnum)], and &[&EbiInputTypeEnum!(type)] as input type.
    String(Option<&'static [&'static str]>, Option<&'static str>),

    ///Fields: minimum, maximum, default value.
    Usize(Option<usize>, Option<usize>, Option<usize>),

    //Fields: minimum, maximum, default value.
    Fraction(
        Option<ConstFraction>,
        Option<ConstFraction>,
        Option<ConstFraction>,
    ),
}

impl EbiInputType {
    pub fn default(&self) -> Option<String> {
        match self {
            EbiInputType::Trait(_) => None,
            EbiInputType::Object(_) => None,
            EbiInputType::AnyObject => None,
            EbiInputType::FileHandler => None,
            EbiInputType::String(_, string) => match string {
                Some(string) => Some(string.to_string()),
                None => None,
            },
            EbiInputType::Usize(_, _, integer) => match integer {
                Some(integer) => Some(integer.to_string()),
                None => None,
            },
            EbiInputType::Fraction(_, _, default) => match default {
                Some(fraction) => Some(fraction.to_string()),
                None => None,
            },
        }
    }

    pub fn get_article(&self) -> &str {
        match self {
            EbiInputType::Trait(t) => t.get_article(),
            EbiInputType::Object(o) => o.get_article(),
            EbiInputType::AnyObject => "an",
            EbiInputType::String(_, _) => "a",
            EbiInputType::Usize(_, _, _) => "an",
            EbiInputType::FileHandler => "a",
            EbiInputType::Fraction(_, _, _) => "a",
        }
    }

    pub fn get_parser_of_list(traits: &[&EbiInputType]) -> ValueParser {
        match traits[0] {
            EbiInputType::Trait(_) => value_parser!(PathBuf),
            EbiInputType::Object(_) => value_parser!(PathBuf),
            EbiInputType::AnyObject => value_parser!(PathBuf),
            EbiInputType::String(_, _) => value_parser!(String).into(),
            EbiInputType::Usize(_, _, _) => value_parser!(usize).into(),
            EbiInputType::FileHandler => value_parser!(EbiFileHandler).into(),
            EbiInputType::Fraction(_, _, _) => value_parser!(FractionNotParsedYet).into(),
        }
    }

    pub fn get_possible_inputs(traits: &[&EbiInputType]) -> Vec<String> {
        let mut result = HashSet::new();

        for input_type in traits {
            match input_type {
                EbiInputType::Trait(t) => {
                    result.extend(Self::show_file_handlers(t.get_file_handlers()));
                }
                EbiInputType::Object(o) => {
                    result.extend(Self::show_file_handlers(get_file_handlers(o)));
                    result.extend(Self::show_file_handlers(get_file_handlers_fallible(o)));
                }
                EbiInputType::AnyObject => {
                    result.extend(Self::show_file_handlers(EBI_FILE_HANDLERS.iter().collect()));
                }
                EbiInputType::String(Some(allowed_values), _) => {
                    result.insert(format!("either one of `{}`", allowed_values.join("`, `")));
                }
                EbiInputType::String(None, _) => {
                    result.insert("text".to_string());
                }
                EbiInputType::Usize(Some(min), Some(max), _) => {
                    result.insert(format!("integer between {} and {}", min, max));
                }
                EbiInputType::Usize(None, Some(max), _) => {
                    result.insert(format!("integer below or equal to {}", max));
                }
                EbiInputType::Usize(Some(min), _, _) => {
                    result.insert(format!("integer above or equal to {}", min));
                }
                EbiInputType::Usize(_, _, _) => {
                    result.insert("integer".to_string());
                }
                EbiInputType::FileHandler => {
                    let extensions: Vec<String> = EBI_FILE_HANDLERS
                        .iter()
                        .filter_map(|file_type| {
                            if file_type.validator.is_some() {
                                Some(file_type.file_extension.to_string())
                            } else {
                                None
                            }
                        })
                        .collect();
                    result.insert(
                        "the file extension of any file type supported by Ebi (".to_owned()
                            + extensions.join_with(", ", " or ").as_str()
                            + ")",
                    );
                }
                EbiInputType::Fraction(Some(min), Some(max), _) => {
                    result.insert(format!("fraction between {} and {}", min, max));
                }
                EbiInputType::Fraction(None, Some(max), _) => {
                    result.insert(format!("fraction below {}", max));
                }
                EbiInputType::Fraction(Some(min), None, _) => {
                    result.insert(format!("fraction above {}", min));
                }
                EbiInputType::Fraction(None, None, _) => {
                    result.insert("fraction".to_string());
                }
            };
        }

        result.into_iter().collect::<Vec<_>>()
    }

    pub fn get_possible_inputs_with_latex(input_types: &[&EbiInputType]) -> Vec<String> {
        let mut result = HashSet::new();

        for input_type in input_types {
            match input_type {
                EbiInputType::Trait(t) => {
                    result.extend(Self::show_file_handlers_latex(t.get_file_handlers()));
                }
                EbiInputType::Object(o) => {
                    result.extend(Self::show_file_handlers_latex(get_file_handlers(o)));
                    result.extend(
                        Self::show_file_handlers_latex(get_file_handlers_fallible(o))
                            .into_iter()
                            .map(|mut s| {
                                s += "\\textsuperscript{\\textdagger}";
                                s
                            }),
                    );
                }
                EbiInputType::AnyObject => {
                    result.extend(Self::show_file_handlers_latex(
                        EBI_FILE_HANDLERS.iter().collect(),
                    ));
                }
                EbiInputType::String(Some(allowed_values), _) => {
                    result.insert(format!(
                        "either one of `{}`",
                        allowed_values.join_with("`, `", "` or `")
                    ));
                }
                EbiInputType::String(None, _) => {
                    result.insert("text".to_string());
                }
                EbiInputType::Usize(Some(min), Some(max), _) => {
                    result.insert(format!("integer between {} and {}", min, max));
                }
                EbiInputType::Usize(None, Some(max), _) => {
                    result.insert(format!("integer below {}", max));
                }
                EbiInputType::Usize(Some(min), _, _) => {
                    result.insert(format!("integer above {}", min));
                }
                EbiInputType::Usize(_, _, _) => {
                    result.insert("integer".to_string());
                }
                EbiInputType::FileHandler => {
                    let extensions: Vec<String> = EBI_FILE_HANDLERS
                        .iter()
                        .filter_map(|file_type| {
                            if file_type.validator.is_some() {
                                Some(file_type.file_extension.to_string())
                            } else {
                                None
                            }
                        })
                        .collect();
                    result.insert(
                        "the file extension of any file type supported by Ebi (".to_owned()
                            + extensions.join_with(", ", " or ").as_str()
                            + ")",
                    );
                }
                EbiInputType::Fraction(Some(min), Some(max), _) => {
                    result.insert(format!("fraction between {} and {}", min, max));
                }
                EbiInputType::Fraction(None, Some(max), _) => {
                    result.insert(format!("fraction below {}", max));
                }
                EbiInputType::Fraction(Some(min), _, _) => {
                    result.insert(format!("fraction above {}", min));
                }
                EbiInputType::Fraction(_, _, _) => {
                    result.insert("fraction".to_string());
                }
            };
        }

        result.into_iter().collect::<Vec<_>>()
    }

    pub fn get_possible_inputs_with_html(input_types: &[&EbiInputType]) -> Vec<String> {
        let mut result = BTreeSet::new();

        for input_type in input_types {
            match input_type {
                EbiInputType::Trait(t) => {
                    result.extend(Self::show_file_handlers_html(t.get_file_handlers()));
                }
                EbiInputType::Object(o) => {
                    result.extend(Self::show_file_handlers_html(get_file_handlers(o)));
                    result.extend(
                        Self::show_file_handlers_html(get_file_handlers_fallible(o))
                            .into_iter()
                            .map(|mut s| {
                                s += "<span title=\"This file type is converted on import, and this conversion may fail.\">*</span>";
                                s
                            }),
                    );
                }
                EbiInputType::AnyObject => {
                    result.extend(Self::show_file_handlers_html(
                        EBI_FILE_HANDLERS.iter().collect(),
                    ));
                }
                EbiInputType::String(Some(allowed_values), _) => {
                    result.extend(allowed_values.iter().map(|x| format!("`{}`", x)));
                }
                EbiInputType::String(None, _) => {
                    result.insert("text".to_string());
                }
                EbiInputType::Usize(Some(min), Some(max), _) => {
                    result.insert(format!("integer between {} and {}", min, max));
                }
                EbiInputType::Usize(None, Some(max), _) => {
                    result.insert(format!("integer below {}", max));
                }
                EbiInputType::Usize(Some(min), _, _) => {
                    result.insert(format!("integer above {}", min));
                }
                EbiInputType::Usize(_, _, _) => {
                    result.insert("integer".to_string());
                }
                EbiInputType::FileHandler => {
                    let extensions: Vec<String> = EBI_FILE_HANDLERS
                        .iter()
                        .filter_map(|file_type| {
                            if file_type.validator.is_some() {
                                Some(file_type.file_extension.to_string())
                            } else {
                                None
                            }
                        })
                        .collect();
                    result.insert(
                        "the file extension of any file type supported by Ebi (".to_owned()
                            + extensions.join_with(", ", " or ").as_str()
                            + ")",
                    );
                }
                EbiInputType::Fraction(Some(min), Some(max), _) => {
                    result.insert(format!("fraction between {} and {}", min, max));
                }
                EbiInputType::Fraction(None, Some(max), _) => {
                    result.insert(format!("fraction below {}", max));
                }
                EbiInputType::Fraction(Some(min), _, _) => {
                    result.insert(format!("fraction above {}", min));
                }
                EbiInputType::Fraction(_, _, _) => {
                    result.insert("fraction".to_string());
                }
            };
        }

        result.into_iter().collect::<Vec<_>>()
    }

    pub fn get_possible_input_extensions(input_types: &[&'static EbiInputType]) -> Vec<String> {
        let mut result = BTreeSet::new();

        for input_type in input_types {
            match input_type {
                EbiInputType::Trait(t) => {
                    result.extend(
                        t.get_file_handlers()
                            .iter()
                            .map(|fh| fh.file_extension.to_string()),
                    );
                }
                EbiInputType::Object(o) => {
                    result.extend(
                        get_file_handlers(o)
                            .iter()
                            .map(|fh| fh.file_extension.to_string()),
                    );
                    result.extend(
                        get_file_handlers_fallible(o)
                            .iter()
                            .map(|fh| fh.file_extension.to_string()),
                    );
                }
                EbiInputType::AnyObject => {
                    result.extend(
                        EBI_FILE_HANDLERS
                            .iter()
                            .map(|fh| fh.file_extension.to_string()),
                    );
                }
                _ => {}
            }
        }
        result.into_iter().collect::<Vec<_>>()
    }

    pub fn get_possible_inputs_with_html_short(input_types: &[&EbiInputType]) -> Vec<String> {
        let mut result = BTreeSet::new();

        for input_type in input_types {
            match input_type {
                EbiInputType::Trait(t) => {
                    result.extend(Self::show_file_handlers_html_short(t.get_file_handlers()));
                }
                EbiInputType::Object(o) => {
                    result.extend(Self::show_file_handlers_html_short(get_file_handlers(o)));
                    result.extend(
                        Self::show_file_handlers_html_short(get_file_handlers_fallible(o))
                            .into_iter()
                            .map(|mut s| {
                                s += "<span title=\"This file type is converted on import, and this conversion may fail.\">*</span>";
                                s
                            }),
                    );
                }
                EbiInputType::AnyObject => {
                    result.extend(Self::show_file_handlers_html_short(
                        EBI_FILE_HANDLERS.iter().collect(),
                    ));
                }
                EbiInputType::String(Some(allowed_values), _) => {
                    result.extend(allowed_values.iter().map(|x| format!("`{}`", x)));
                }
                EbiInputType::String(None, _) => {
                    result.insert("text".to_string());
                }
                EbiInputType::Usize(Some(min), Some(max), _) => {
                    result.insert(format!("integer between {} and {}", min, max));
                }
                EbiInputType::Usize(None, Some(max), _) => {
                    result.insert(format!("integer below {}", max));
                }
                EbiInputType::Usize(Some(min), _, _) => {
                    result.insert(format!("integer above {}", min));
                }
                EbiInputType::Usize(_, _, _) => {
                    result.insert("integer".to_string());
                }
                EbiInputType::FileHandler => {
                    let extensions: Vec<String> = EBI_FILE_HANDLERS
                        .iter()
                        .filter_map(|file_type| {
                            if file_type.validator.is_some() {
                                Some(file_type.file_extension.to_string())
                            } else {
                                None
                            }
                        })
                        .collect();
                    result.insert(
                        "the file extension of any file type supported by Ebi (".to_owned()
                            + extensions.join_with(", ", " or ").as_str()
                            + ")",
                    );
                }
                EbiInputType::Fraction(Some(min), Some(max), _) => {
                    result.insert(format!("fraction between {} and {}", min, max));
                }
                EbiInputType::Fraction(None, Some(max), _) => {
                    result.insert(format!("fraction below {}", max));
                }
                EbiInputType::Fraction(Some(min), _, _) => {
                    result.insert(format!("fraction above {}", min));
                }
                EbiInputType::Fraction(_, _, _) => {
                    result.insert("fraction".to_string());
                }
            };
        }

        result.into_iter().collect::<Vec<_>>()
    }

    pub fn possible_inputs_as_strings_with_articles(
        traits: &[&EbiInputType],
        last_connector: &str,
    ) -> String {
        let list = Self::get_possible_inputs(traits);
        list.join_with(", ", last_connector)
    }

    pub fn show_file_handlers(file_handlers: Vec<&'static EbiFileHandler>) -> Vec<String> {
        file_handlers
            .iter()
            .map(|file_handler| format!("{}", file_handler))
            .collect::<Vec<_>>()
    }

    pub fn show_file_handlers_latex(file_handlers: Vec<&'static EbiFileHandler>) -> Vec<String> {
        file_handlers
            .iter()
            .map(|file_handler| {
                format!(
                    "\\hyperref[filehandler:{}]{{{}}}",
                    file_handler.name, file_handler
                )
            })
            .collect::<Vec<_>>()
    }

    pub fn show_file_handlers_html(file_handlers: Vec<&'static EbiFileHandler>) -> Vec<String> {
        file_handlers
            .iter()
            .map(|file_handler| {
                format!(
                    "{} (<a href=\"{}#{}\">.{}</a>)",
                    file_handler.name,
                    FILE_HANDLERS_PAGE,
                    file_handler.file_extension,
                    file_handler.file_extension
                )
            })
            .collect::<Vec<_>>()
    }

    pub fn show_file_handlers_html_short(
        file_handlers: Vec<&'static EbiFileHandler>,
    ) -> Vec<String> {
        file_handlers
            .iter()
            .map(|file_handler| {
                format!(
                    "<a href=\"{}#{}\">.{}</a>",
                    FILE_HANDLERS_PAGE, file_handler.file_extension, file_handler.file_extension
                )
            })
            .collect::<Vec<_>>()
    }

    pub fn get_applicable_commands(&self) -> BTreeSet<Vec<&'static EbiCommand>> {
        let mut result = EBI_COMMANDS.get_command_paths();
        result.retain(|path| {
            if let EbiCommand::Command { input_types, .. } = path[path.len() - 1] {
                for input_typess in input_types.iter() {
                    for input_typesss in input_typess.iter() {
                        if input_typesss == &self {
                            return true;
                        }
                    }
                }
            }
            false
        });
        result
    }
}

impl Display for EbiInputType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EbiInputType::Trait(t) => Display::fmt(t, f),
            EbiInputType::Object(o) => Display::fmt(o, f),
            EbiInputType::AnyObject => write!(f, "object"),
            EbiInputType::String(Some(allowed_values), _) => {
                write!(f, "either one of `{}`", allowed_values.join("`, `"))
            }
            EbiInputType::String(None, _) => write!(f, "text"),
            EbiInputType::Usize(Some(min), Some(max), _) => {
                write!(
                    f,
                    "integer between {} (inclusive) and {} (inclusive)",
                    min, max
                )
            }
            EbiInputType::Usize(None, Some(max), _) => {
                write!(f, "integer below or equal to {}", max)
            }
            EbiInputType::Usize(Some(min), None, _) => {
                write!(f, "integer equal to or above {}", min)
            }
            EbiInputType::Usize(None, None, _) => {
                write!(f, "integer")
            }
            EbiInputType::FileHandler => write!(f, "file"),
            EbiInputType::Fraction(Some(min), Some(max), _) => {
                write!(
                    f,
                    "fraction between {} (inclusive) and {} (inclusive)",
                    min, max
                )
            }
            EbiInputType::Fraction(None, Some(max), _) => {
                write!(f, "fraction below or equal to {}", max)
            }
            EbiInputType::Fraction(Some(min), None, _) => {
                write!(f, "fraction equal to or above {}", min)
            }
            EbiInputType::Fraction(None, None, _) => {
                write!(f, "fraction")
            }
        }
    }
}

/**
 * Returns true if one of the input types has a default.
 */
pub fn default(input_types: &[&EbiInputType]) -> Option<String> {
    input_types
        .iter()
        .filter_map(|input_type| input_type.default())
        .next()
}

#[derive(Debug, Clone)]
pub enum EbiTraitImporter {
    FiniteLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<Box<dyn EbiTraitFiniteLanguage>>,
        &'static [ImporterParameter],
    ),
    FiniteStochasticLanguage(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<Box<dyn EbiTraitFiniteStochasticLanguage>>,
        &'static [ImporterParameter],
    ),
    QueriableStochasticLanguage(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<Box<dyn EbiTraitQueriableStochasticLanguage>>,
        &'static [ImporterParameter],
    ),
    IterableLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<Box<dyn EbiTraitIterableLanguage>>,
        &'static [ImporterParameter],
    ),
    IterableStochasticLanguage(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<Box<dyn EbiTraitIterableStochasticLanguage>>,
        &'static [ImporterParameter],
    ),
    EventLog(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<Box<dyn EbiTraitEventLog>>,
        &'static [ImporterParameter],
    ),
    EventLogEventAttributes(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<Box<dyn EbiTraitEventLogEventAttributes>>,
        &'static [ImporterParameter],
    ),
    EventLogTraceAttributes(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<Box<dyn EbiTraitEventLogTraceAttributes>>,
        &'static [ImporterParameter],
    ),
    Semantics(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiTraitSemantics>,
        &'static [ImporterParameter],
    ),
    StochasticSemantics(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiTraitStochasticSemantics>,
        &'static [ImporterParameter],
    ),
    StochasticPartiallyOrderedSemantics(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<EbiTraitStochasticPartiallyOrderedSemantics>,
        &'static [ImporterParameter],
    ),
    StochasticDeterministicSemantics(
        fn(
            &mut dyn BufRead,
            &ImporterParameterValues,
        ) -> Result<EbiTraitStochasticDeterministicSemantics>,
        &'static [ImporterParameter],
    ),
    Graphable(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<Box<dyn EbiTraitGraphable>>,
        &'static [ImporterParameter],
    ),
    Activities(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<Box<dyn EbiTraitActivities>>,
        &'static [ImporterParameter],
    ),
}

impl EbiTraitImporter {
    pub fn get_trait(&self) -> EbiTrait {
        match self {
            EbiTraitImporter::FiniteLanguage(_, _) => EbiTrait::FiniteLanguage,
            EbiTraitImporter::FiniteStochasticLanguage(_, _) => EbiTrait::FiniteStochasticLanguage,
            EbiTraitImporter::QueriableStochasticLanguage(_, _) => {
                EbiTrait::QueriableStochasticLanguage
            }
            EbiTraitImporter::IterableLanguage(_, _) => EbiTrait::IterableLanguage,
            EbiTraitImporter::IterableStochasticLanguage(_, _) => {
                EbiTrait::IterableStochasticLanguage
            }
            EbiTraitImporter::EventLog(_, _) => EbiTrait::EventLog,
            EbiTraitImporter::EventLogEventAttributes(_, _) => EbiTrait::EventLogEventAttributes,
            EbiTraitImporter::EventLogTraceAttributes(_, _) => EbiTrait::EventLogTraceAttributes,
            EbiTraitImporter::Semantics(_, _) => EbiTrait::Semantics,
            EbiTraitImporter::StochasticSemantics(_, _) => EbiTrait::StochasticSemantics,
            EbiTraitImporter::StochasticPartiallyOrderedSemantics(_, _) => {
                EbiTrait::StochasticPartiallyOrderedSemantics
            }
            EbiTraitImporter::StochasticDeterministicSemantics(_, _) => {
                EbiTrait::StochasticDeterministicSemantics
            }
            EbiTraitImporter::Graphable(_, _) => EbiTrait::Graphable,
            EbiTraitImporter::Activities(_, _) => EbiTrait::Activities,
        }
    }

    pub fn parameters(&self) -> &'static [ImporterParameter] {
        match self {
            EbiTraitImporter::FiniteLanguage(_, importer_parameters)
            | EbiTraitImporter::FiniteStochasticLanguage(_, importer_parameters)
            | EbiTraitImporter::QueriableStochasticLanguage(_, importer_parameters)
            | EbiTraitImporter::IterableLanguage(_, importer_parameters)
            | EbiTraitImporter::IterableStochasticLanguage(_, importer_parameters)
            | EbiTraitImporter::EventLog(_, importer_parameters)
            | EbiTraitImporter::EventLogEventAttributes(_, importer_parameters)
            | EbiTraitImporter::EventLogTraceAttributes(_, importer_parameters)
            | EbiTraitImporter::Semantics(_, importer_parameters)
            | EbiTraitImporter::StochasticSemantics(_, importer_parameters)
            | EbiTraitImporter::StochasticPartiallyOrderedSemantics(_, importer_parameters)
            | EbiTraitImporter::StochasticDeterministicSemantics(_, importer_parameters)
            | EbiTraitImporter::Graphable(_, importer_parameters)
            | EbiTraitImporter::Activities(_, importer_parameters) => importer_parameters,
        }
    }

    pub fn default_parameter_values(&self) -> ImporterParameterValues {
        let mut result = ImporterParameterValues::new();
        for parameter in self.parameters() {
            result.insert(*parameter, (parameter.default(), false));
        }
        result
    }

    pub fn import(
        &self,
        reader: &mut dyn BufRead,
        parameter_values: &ImporterParameterValues,
    ) -> Result<EbiTraitObject> {
        Ok(match self {
            EbiTraitImporter::FiniteLanguage(f, _) => {
                EbiTraitObject::FiniteLanguage((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::FiniteStochasticLanguage(f, _) => {
                EbiTraitObject::FiniteStochasticLanguage((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::QueriableStochasticLanguage(f, _) => {
                EbiTraitObject::QueriableStochasticLanguage((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::IterableLanguage(f, _) => {
                EbiTraitObject::IterableLanguage((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::IterableStochasticLanguage(f, _) => {
                EbiTraitObject::IterableStochasticLanguage((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::EventLog(f, _) => {
                EbiTraitObject::EventLog((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::EventLogEventAttributes(f, _) => {
                EbiTraitObject::EventLogEventAttributes((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::EventLogTraceAttributes(f, _) => {
                EbiTraitObject::EventLogTraceAttributes((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::Semantics(f, _) => {
                EbiTraitObject::Semantics((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::StochasticSemantics(f, _) => {
                EbiTraitObject::StochasticSemantics((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::StochasticPartiallyOrderedSemantics(f, _) => {
                EbiTraitObject::StochasticPartiallyOrderedSemantics((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::StochasticDeterministicSemantics(f, _) => {
                EbiTraitObject::StochasticDeterministicSemantics((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::Graphable(f, _) => {
                EbiTraitObject::Graphable((f)(reader, parameter_values)?)
            }
            EbiTraitImporter::Activities(f, _) => {
                EbiTraitObject::Activities((f)(reader, parameter_values)?)
            }
        })
    }
}

impl Display for EbiTraitImporter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.get_trait().to_string())
    }
}

#[derive(Clone, IntoStaticStr)]
pub enum EbiObjectImporter {
    BusinessProcessModelAndNotation(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    EventLog(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    EventLogCsv(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    EventLogEventAttributes(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    EventLogOcel(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    EventLogTraceAttributes(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    EventLogXes(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    DirectlyFollowsGraph(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    DirectlyFollowsModel(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticBusinessProcessModelAndNotation(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticDirectlyFollowsModel(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    FiniteLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    FiniteStochasticLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    FiniteStochasticPartiallyOrderedLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    LabelledPetriNet(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticDeterministicFiniteAutomaton(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticNondeterministicFiniteAutomaton(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticLabelledPetriNet(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    LanguageOfAlignments(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    DeterministicFiniteAutomaton(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    ProcessTree(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    Executions(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticLanguageOfAlignments(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
    StochasticProcessTree(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject>,
        &'static [ImporterParameter],
    ),
}

impl EbiObjectImporter {
    pub fn get_type(&self) -> EbiObjectType {
        match self {
            EbiObjectImporter::BusinessProcessModelAndNotation(_, _) => {
                EbiObjectType::BusinessProcessModelAndNotation
            }
            EbiObjectImporter::EventLog(_, _) => EbiObjectType::EventLog,
            EbiObjectImporter::EventLogCsv(_, _) => EbiObjectType::EventLogCsv,
            EbiObjectImporter::EventLogEventAttributes(_, _) => {
                EbiObjectType::EventLogEventAttributes
            }
            EbiObjectImporter::EventLogOcel(_, _) => EbiObjectType::EventLogOcel,
            EbiObjectImporter::EventLogTraceAttributes(_, _) => {
                EbiObjectType::EventLogTraceAttributes
            }
            EbiObjectImporter::EventLogXes(_, _) => EbiObjectType::EventLogXes,
            EbiObjectImporter::DirectlyFollowsGraph(_, _) => EbiObjectType::DirectlyFollowsGraph,
            EbiObjectImporter::DirectlyFollowsModel(_, _) => EbiObjectType::DirectlyFollowsModel,
            EbiObjectImporter::StochasticDirectlyFollowsModel(_, _) => {
                EbiObjectType::StochasticDirectlyFollowsModel
            }
            EbiObjectImporter::FiniteLanguage(_, _) => EbiObjectType::FiniteLanguage,
            EbiObjectImporter::FiniteStochasticLanguage(_, _) => {
                EbiObjectType::FiniteStochasticLanguage
            }
            EbiObjectImporter::FiniteStochasticPartiallyOrderedLanguage(_, _) => {
                EbiObjectType::FiniteStochasticPartiallyOrderedLanguage
            }
            EbiObjectImporter::LabelledPetriNet(_, _) => EbiObjectType::LabelledPetriNet,
            EbiObjectImporter::StochasticBusinessProcessModelAndNotation(_, _) => {
                EbiObjectType::StochasticBusinessProcessModelAndNotation
            }
            EbiObjectImporter::StochasticDeterministicFiniteAutomaton(_, _) => {
                EbiObjectType::StochasticDeterministicFiniteAutomaton
            }
            EbiObjectImporter::StochasticNondeterministicFiniteAutomaton(_, _) => {
                EbiObjectType::StochasticNondeterministicFiniteAutomaton
            }
            EbiObjectImporter::StochasticLabelledPetriNet(_, _) => {
                EbiObjectType::StochasticLabelledPetriNet
            }
            EbiObjectImporter::LanguageOfAlignments(_, _) => EbiObjectType::LanguageOfAlignments,
            EbiObjectImporter::StochasticLanguageOfAlignments(_, _) => {
                EbiObjectType::StochasticLanguageOfAlignments
            }
            EbiObjectImporter::DeterministicFiniteAutomaton(_, _) => {
                EbiObjectType::DeterministicFiniteAutomaton
            }
            EbiObjectImporter::ProcessTree(_, _) => EbiObjectType::ProcessTree,
            EbiObjectImporter::StochasticProcessTree(_, _) => EbiObjectType::StochasticProcessTree,
            EbiObjectImporter::Executions(_, _) => EbiObjectType::Executions,
        }
    }

    pub fn parameters(&self) -> &'static [ImporterParameter] {
        match self {
            EbiObjectImporter::BusinessProcessModelAndNotation(_, parameters) => parameters,
            EbiObjectImporter::EventLog(_, parameters) => parameters,
            EbiObjectImporter::EventLogCsv(_, parameters) => parameters,
            EbiObjectImporter::EventLogEventAttributes(_, parameters) => parameters,
            EbiObjectImporter::EventLogOcel(_, parameters) => parameters,
            EbiObjectImporter::EventLogTraceAttributes(_, parameters) => parameters,
            EbiObjectImporter::EventLogXes(_, parameters) => parameters,
            EbiObjectImporter::DirectlyFollowsGraph(_, parameters) => parameters,
            EbiObjectImporter::DirectlyFollowsModel(_, parameters) => parameters,
            EbiObjectImporter::StochasticBusinessProcessModelAndNotation(_, parameters) => {
                parameters
            }
            EbiObjectImporter::StochasticDirectlyFollowsModel(_, parameters) => parameters,
            EbiObjectImporter::FiniteLanguage(_, parameters) => parameters,
            EbiObjectImporter::FiniteStochasticLanguage(_, parameters) => parameters,
            EbiObjectImporter::FiniteStochasticPartiallyOrderedLanguage(_, parameters) => {
                parameters
            }
            EbiObjectImporter::LabelledPetriNet(_, parameters) => parameters,
            EbiObjectImporter::StochasticDeterministicFiniteAutomaton(_, parameters) => parameters,
            EbiObjectImporter::StochasticNondeterministicFiniteAutomaton(_, parameters) => {
                parameters
            }
            EbiObjectImporter::StochasticLabelledPetriNet(_, parameters) => parameters,
            EbiObjectImporter::LanguageOfAlignments(_, parameters) => parameters,
            EbiObjectImporter::DeterministicFiniteAutomaton(_, parameters) => parameters,
            EbiObjectImporter::ProcessTree(_, parameters) => parameters,
            EbiObjectImporter::Executions(_, parameters) => parameters,
            EbiObjectImporter::StochasticLanguageOfAlignments(_, parameters) => parameters,
            EbiObjectImporter::StochasticProcessTree(_, parameters) => parameters,
        }
    }

    pub fn default_parameter_values(&self) -> ImporterParameterValues {
        let mut result = ImporterParameterValues::new();
        for parameter in self.parameters() {
            result.insert(*parameter, (parameter.default(), false));
        }
        result
    }

    pub fn get_importer(
        &self,
    ) -> fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject> {
        match self {
            EbiObjectImporter::BusinessProcessModelAndNotation(importer, _) => *importer,
            EbiObjectImporter::EventLog(importer, _) => *importer,
            EbiObjectImporter::EventLogCsv(importer, _) => *importer,
            EbiObjectImporter::EventLogEventAttributes(importer, _) => *importer,
            EbiObjectImporter::EventLogOcel(importer, _) => *importer,
            EbiObjectImporter::EventLogTraceAttributes(importer, _) => *importer,
            EbiObjectImporter::EventLogXes(importer, _) => *importer,
            EbiObjectImporter::DirectlyFollowsGraph(importer, _) => *importer,
            EbiObjectImporter::DirectlyFollowsModel(importer, _) => *importer,
            EbiObjectImporter::StochasticDirectlyFollowsModel(importer, _) => *importer,
            EbiObjectImporter::FiniteLanguage(importer, _) => *importer,
            EbiObjectImporter::FiniteStochasticLanguage(importer, _) => *importer,
            EbiObjectImporter::FiniteStochasticPartiallyOrderedLanguage(importer, _) => *importer,
            EbiObjectImporter::LabelledPetriNet(importer, _) => *importer,
            EbiObjectImporter::StochasticBusinessProcessModelAndNotation(importer, _) => *importer,
            EbiObjectImporter::StochasticDeterministicFiniteAutomaton(importer, _) => *importer,
            EbiObjectImporter::StochasticNondeterministicFiniteAutomaton(importer, _) => *importer,
            EbiObjectImporter::StochasticLabelledPetriNet(importer, _) => *importer,
            EbiObjectImporter::LanguageOfAlignments(importer, _) => *importer,
            EbiObjectImporter::StochasticLanguageOfAlignments(importer, _) => *importer,
            EbiObjectImporter::DeterministicFiniteAutomaton(importer, _) => *importer,
            EbiObjectImporter::ProcessTree(importer, _) => *importer,
            EbiObjectImporter::StochasticProcessTree(importer, _) => *importer,
            EbiObjectImporter::Executions(importer, _) => *importer,
        }
    }
}

impl Display for EbiObjectImporter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let right: &'static str = self.into();
        write!(f, "{}", right)
    }
}

impl Debug for EbiObjectImporter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let right: &'static str = self.into();
        write!(f, "{}", right)
    }
}

#[derive(Debug)]
pub enum FallibleImporterError {
    ImporterError(Error),
    ConversionError(Error),
}

#[derive(Clone, IntoStaticStr)]
pub enum EbiObjectImporterFallible {
    BusinessProcessModelAndNotation(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    EventLog(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    EventLogTraceAttributes(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    EventLogCsv(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    EventLogOcel(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    EventLogXes(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    DirectlyFollowsGraph(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    DirectlyFollowsModel(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    StochasticDirectlyFollowsModel(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    FiniteLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    FiniteStochasticLanguage(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    LabelledPetriNet(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    StochasticDeterministicFiniteAutomaton(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    StochasticNondeterministicFiniteAutomaton(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    StochasticLabelledPetriNet(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    LanguageOfAlignments(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    DeterministicFiniteAutomaton(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    ProcessTree(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    Executions(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    StochasticLanguageOfAlignments(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
    StochasticProcessTree(
        fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>,
        &'static [ImporterParameter],
    ),
}

impl EbiObjectImporterFallible {
    pub fn get_type(&self) -> EbiObjectType {
        match self {
            EbiObjectImporterFallible::BusinessProcessModelAndNotation(_, _) => {
                EbiObjectType::BusinessProcessModelAndNotation
            }
            EbiObjectImporterFallible::EventLog(_, _) => EbiObjectType::EventLog,
            EbiObjectImporterFallible::EventLogCsv(_, _) => EbiObjectType::EventLogCsv,
            EbiObjectImporterFallible::EventLogOcel(_, _) => EbiObjectType::EventLogOcel,
            EbiObjectImporterFallible::EventLogTraceAttributes(_, _) => {
                EbiObjectType::EventLogTraceAttributes
            }
            EbiObjectImporterFallible::EventLogXes(_, _) => EbiObjectType::EventLogXes,
            EbiObjectImporterFallible::DirectlyFollowsGraph(_, _) => {
                EbiObjectType::DirectlyFollowsGraph
            }
            EbiObjectImporterFallible::DirectlyFollowsModel(_, _) => {
                EbiObjectType::DirectlyFollowsModel
            }
            EbiObjectImporterFallible::StochasticDirectlyFollowsModel(_, _) => {
                EbiObjectType::StochasticDirectlyFollowsModel
            }
            EbiObjectImporterFallible::FiniteLanguage(_, _) => EbiObjectType::FiniteLanguage,
            EbiObjectImporterFallible::FiniteStochasticLanguage(_, _) => {
                EbiObjectType::FiniteStochasticLanguage
            }
            EbiObjectImporterFallible::LabelledPetriNet(_, _) => EbiObjectType::LabelledPetriNet,
            EbiObjectImporterFallible::StochasticDeterministicFiniteAutomaton(_, _) => {
                EbiObjectType::StochasticDeterministicFiniteAutomaton
            }
            EbiObjectImporterFallible::StochasticNondeterministicFiniteAutomaton(_, _) => {
                EbiObjectType::StochasticNondeterministicFiniteAutomaton
            }
            EbiObjectImporterFallible::StochasticLabelledPetriNet(_, _) => {
                EbiObjectType::StochasticLabelledPetriNet
            }
            EbiObjectImporterFallible::LanguageOfAlignments(_, _) => {
                EbiObjectType::LanguageOfAlignments
            }
            EbiObjectImporterFallible::StochasticLanguageOfAlignments(_, _) => {
                EbiObjectType::StochasticLanguageOfAlignments
            }
            EbiObjectImporterFallible::DeterministicFiniteAutomaton(_, _) => {
                EbiObjectType::DeterministicFiniteAutomaton
            }
            EbiObjectImporterFallible::ProcessTree(_, _) => EbiObjectType::ProcessTree,
            EbiObjectImporterFallible::StochasticProcessTree(_, _) => {
                EbiObjectType::StochasticProcessTree
            }
            EbiObjectImporterFallible::Executions(_, _) => EbiObjectType::Executions,
        }
    }

    pub fn parameters(&self) -> &'static [ImporterParameter] {
        match self {
            EbiObjectImporterFallible::BusinessProcessModelAndNotation(_, parameters) => parameters,
            EbiObjectImporterFallible::EventLog(_, parameters) => parameters,
            EbiObjectImporterFallible::EventLogCsv(_, parameters) => parameters,
            EbiObjectImporterFallible::EventLogOcel(_, parameters) => parameters,
            EbiObjectImporterFallible::EventLogTraceAttributes(_, parameters) => parameters,
            EbiObjectImporterFallible::EventLogXes(_, parameters) => parameters,
            EbiObjectImporterFallible::DirectlyFollowsGraph(_, parameters) => parameters,
            EbiObjectImporterFallible::DirectlyFollowsModel(_, parameters) => parameters,
            EbiObjectImporterFallible::StochasticDirectlyFollowsModel(_, parameters) => parameters,
            EbiObjectImporterFallible::FiniteLanguage(_, parameters) => parameters,
            EbiObjectImporterFallible::FiniteStochasticLanguage(_, parameters) => parameters,
            EbiObjectImporterFallible::LabelledPetriNet(_, parameters) => parameters,
            EbiObjectImporterFallible::StochasticDeterministicFiniteAutomaton(_, parameters) => {
                parameters
            }
            EbiObjectImporterFallible::StochasticNondeterministicFiniteAutomaton(_, parameters) => {
                parameters
            }
            EbiObjectImporterFallible::StochasticLabelledPetriNet(_, parameters) => parameters,
            EbiObjectImporterFallible::LanguageOfAlignments(_, parameters) => parameters,
            EbiObjectImporterFallible::DeterministicFiniteAutomaton(_, parameters) => parameters,
            EbiObjectImporterFallible::ProcessTree(_, parameters) => parameters,
            EbiObjectImporterFallible::Executions(_, parameters) => parameters,
            EbiObjectImporterFallible::StochasticLanguageOfAlignments(_, parameters) => parameters,
            EbiObjectImporterFallible::StochasticProcessTree(_, parameters) => parameters,
        }
    }

    pub fn default_parameter_values(&self) -> ImporterParameterValues {
        let mut result = ImporterParameterValues::new();
        for parameter in self.parameters() {
            result.insert(*parameter, (parameter.default(), false));
        }
        result
    }

    pub fn get_importer(
        &self,
    ) -> fn(&mut dyn BufRead, &ImporterParameterValues) -> Result<EbiObject, FallibleImporterError>
    {
        match self {
            EbiObjectImporterFallible::BusinessProcessModelAndNotation(importer, _) => *importer,
            EbiObjectImporterFallible::EventLog(importer, _) => *importer,
            EbiObjectImporterFallible::EventLogCsv(importer, _) => *importer,
            EbiObjectImporterFallible::EventLogOcel(importer, _) => *importer,
            EbiObjectImporterFallible::EventLogTraceAttributes(importer, _) => *importer,
            EbiObjectImporterFallible::EventLogXes(importer, _) => *importer,
            EbiObjectImporterFallible::DirectlyFollowsGraph(importer, _) => *importer,
            EbiObjectImporterFallible::DirectlyFollowsModel(importer, _) => *importer,
            EbiObjectImporterFallible::StochasticDirectlyFollowsModel(importer, _) => *importer,
            EbiObjectImporterFallible::FiniteLanguage(importer, _) => *importer,
            EbiObjectImporterFallible::FiniteStochasticLanguage(importer, _) => *importer,
            EbiObjectImporterFallible::LabelledPetriNet(importer, _) => *importer,
            EbiObjectImporterFallible::StochasticDeterministicFiniteAutomaton(importer, _) => {
                *importer
            }
            EbiObjectImporterFallible::StochasticNondeterministicFiniteAutomaton(importer, _) => {
                *importer
            }
            EbiObjectImporterFallible::StochasticLabelledPetriNet(importer, _) => *importer,
            EbiObjectImporterFallible::LanguageOfAlignments(importer, _) => *importer,
            EbiObjectImporterFallible::StochasticLanguageOfAlignments(importer, _) => *importer,
            EbiObjectImporterFallible::DeterministicFiniteAutomaton(importer, _) => *importer,
            EbiObjectImporterFallible::ProcessTree(importer, _) => *importer,
            EbiObjectImporterFallible::StochasticProcessTree(importer, _) => *importer,
            EbiObjectImporterFallible::Executions(importer, _) => *importer,
        }
    }
}

impl Debug for EbiObjectImporterFallible {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let right: &'static str = self.into();
        write!(f, "{}", right)
    }
}

impl Display for EbiObjectImporterFallible {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let right: &'static str = self.into();
        write!(f, "{}", right)
    }
}

pub fn get_reader_file(from_file: &PathBuf) -> Result<MultipleReader> {
    if from_file.as_os_str() == "-" {
        return MultipleReader::from_stdin();
    } else {
        let file = File::open(from_file)
            .with_context(|| format!("Could not read file `{}`.", from_file.display()))?;
        return Ok(MultipleReader::from_file(file));
    }
}

pub fn get_reader(cli_matches: &ArgMatches, cli_id: &str) -> Result<MultipleReader> {
    if let Some(from_file) = cli_matches.try_get_one::<PathBuf>(cli_id)? {
        if from_file.as_os_str() == "-" {
            return MultipleReader::from_stdin();
        } else {
            let file = File::open(from_file)
                .with_context(|| format!("Could not read file `{}`.", from_file.display()))?;
            return Ok(MultipleReader::from_file(file));
        }
    } else {
        return Err(anyhow!(
            "No argument given, or it could not be parsed as a path."
        ));
    }
}

pub fn read_as_trait(
    etrait: &EbiTrait,
    reader: &mut MultipleReader,
    cli_matches: Option<&ArgMatches>,
    input_index: usize,
) -> Result<(EbiTraitObject, &'static EbiFileHandler)> {
    let mut error = None;
    for file_handler in EBI_FILE_HANDLERS {
        for importer in file_handler.trait_importers {
            if &importer.get_trait() == etrait {
                //attempt to import
                match ebi_importer_parameters::extract_parameter_values(
                    cli_matches,
                    importer.parameters(),
                    input_index,
                ) {
                    Ok(importer_parameter_values) => {
                        match importer.import(reader.get().with_context(|| "Obtaining reader.")?.as_mut(), &importer_parameter_values)
                            .with_context(|| {format!("The last attempted importer was: {}.", file_handler)})
                            .with_context(|| format!("Attempting to parse file as either {}.\nIf you know the type of your file, use `Ebi {}` to check it.", EbiInputType::show_file_handlers(etrait.get_file_handlers()).join(", "), ebi_validate!())) {
                            Ok(object) => {return Ok((object, file_handler));}, //object parsed, return it
                            Err(err) => {error = Some(err);}
                        }
                    }
                    Err(e) => error = Some(e),
                }
            }
        }
    }
    Err(error.unwrap())
}

pub fn read_as_object(
    etype: &EbiObjectType,
    reader: &mut MultipleReader,
    cli_matches: Option<&ArgMatches>,
    input_index: usize,
) -> Result<(EbiObject, &'static EbiFileHandler)> {
    let mut conversion_error = None;
    for file_handler in EBI_FILE_HANDLERS {
        if let Ok(object) = read_as_object_with_file_handler(
            etype,
            reader,
            cli_matches,
            input_index,
            &mut conversion_error,
            file_handler,
        ) {
            return Ok((object, file_handler));
        }
    }

    if let Some((error, file_handler)) = conversion_error {
        Err(error).context(anyhow!(
            "File was recognised as {} .{}, but could not be converted to {}.",
            file_handler.article,
            file_handler.file_extension,
            etype
        ))
    } else {
        Err(anyhow!(
            "File could not be recognised. If you know the file format, try validating it with `Ebi validate [file type]'."
        ))
    }
}

pub fn read_as_object_with_file_handler(
    etype: &EbiObjectType,
    reader: &mut MultipleReader,
    cli_matches: Option<&ArgMatches>,
    input_index: usize,
    conversion_error: &mut Option<(Error, &EbiFileHandler)>,
    file_handler: &'static EbiFileHandler,
) -> Result<EbiObject> {
    for importer in file_handler.object_importers {
        if &importer.get_type() == etype {
            //attempt to import
            match ebi_importer_parameters::extract_parameter_values(
                cli_matches,
                importer.parameters(),
                input_index,
            ) {
                Ok(importer_parameter_values) => {
                    if let Ok(object) = (importer.get_importer())(
                        reader.get().context("Could not obtain reader.")?.as_mut(),
                        &importer_parameter_values,
                    ) {
                        //object parsed; return it
                        return Ok(object);
                    }
                }
                Err(_) => {}
            }
        }
    }
    for importer in file_handler.object_importers_fallible {
        if &importer.get_type() == etype {
            //attempt to import
            match ebi_importer_parameters::extract_parameter_values(
                cli_matches,
                importer.parameters(),
                input_index,
            ) {
                Ok(importer_parameter_values) => {
                    match (importer.get_importer())(
                        reader.get().context("Could not obtain reader.")?.as_mut(),
                        &importer_parameter_values,
                    ) {
                        Ok(object) => return Ok(object), //object parsed; return it
                        Err(FallibleImporterError::ConversionError(e)) => {
                            *conversion_error = Some((e, file_handler));
                        }
                        Err(FallibleImporterError::ImporterError(_)) => {}
                    }
                }
                Err(_) => {}
            }
        }
    }
    Err(anyhow!("not parsed"))
}

pub fn read_as_any_object(
    reader: &mut MultipleReader,
    cli_matches: Option<&ArgMatches>,
    input_index: usize,
) -> Result<(EbiObject, &'static EbiFileHandler)> {
    for file_handler in EBI_FILE_HANDLERS {
        //attempt to import
        for importer in file_handler.object_importers {
            match ebi_importer_parameters::extract_parameter_values(
                cli_matches,
                importer.parameters(),
                input_index,
            ) {
                Ok(importer_parameter_values) => {
                    if let Ok(object) = (importer.get_importer())(
                        reader.get().context("Could not obtain reader.")?.as_mut(),
                        &importer_parameter_values,
                    ) {
                        //object parsed; return it
                        return Ok((object, file_handler));
                    }
                }
                Err(_) => {}
            }
        }
    }
    Err(anyhow!(
        "File could not be recognised. If you know the file type, try validating it with `Ebi validate [file type]'."
    ))
}

/// Attempt to parse an input as any of the given input types. Returns the last error if unsuccessful.
pub fn attempt_parse(
    input_types: &[&'static EbiInputType],
    mut reader: MultipleReader,
) -> Result<EbiInput> {
    //an input may be of several types; go through each of them
    let mut error = None;
    for input_type in input_types.iter() {
        //try to parse the input as this type
        match input_type {
            EbiInputType::Trait(etrait) => {
                //try to parse a trait
                match read_as_trait(etrait, &mut reader, None, 0)
                    .with_context(|| format!("Parsing as the trait `{}`.", etrait))
                {
                    Ok((object, file_handler)) => return Ok(EbiInput::Trait(object, file_handler)),
                    Err(e) => error = Some(e),
                }
            }
            EbiInputType::Object(etype) => {
                //try to parse a specific object
                match read_as_object(etype, &mut reader, None, 0)
                    .with_context(|| format!("Parsing as the object type `{}`.", etype))
                {
                    Ok((object, file_handler)) => {
                        return Ok(EbiInput::Object(object, file_handler));
                    }
                    Err(e) => error = Some(e),
                }
            }
            EbiInputType::AnyObject => {
                match read_as_any_object(&mut reader, None, 0).context("Parsing as any object.") {
                    Ok((object, file_handler)) => {
                        return Ok(EbiInput::Object(object, file_handler));
                    }
                    Err(e) => error = Some(e),
                }
            }
            EbiInputType::FileHandler => {
                if let MultipleReader::String(string) = &reader {
                    if let Ok(value) = string.parse::<EbiFileHandler>() {
                        return Ok(EbiInput::FileHandler(value));
                    }
                };
            }
            EbiInputType::String(None, _) => {
                if let MultipleReader::String(string) = &reader {
                    return Ok(EbiInput::String(string.clone(), &input_type));
                } else {
                    return Err(anyhow!(
                        "Cannot get a string from a non-string multireader."
                    ));
                }
            }
            EbiInputType::String(Some(allowed_values), _) => {
                if let MultipleReader::String(string) = &reader {
                    if allowed_values.contains(&string.as_str()) {
                        return Ok(EbiInput::String(string.clone(), &input_type));
                    } else {
                        error = Some(anyhow!("value should be one of {:?}", allowed_values));
                    }
                } else {
                    return Err(anyhow!(
                        "Cannot get a string from a non-string multireader."
                    ));
                }
            }
            EbiInputType::Usize(min, max, _) => {
                if let MultipleReader::String(string) = &reader {
                    if let Ok(value) = string.parse::<usize>() {
                        match (min, max) {
                            (None, None) => {
                                return Ok(EbiInput::Usize(value.clone(), &input_type));
                            }
                            (None, Some(max)) => {
                                if max < &value {
                                    error = Some(anyhow!("value should be at most {}", max));
                                } else {
                                    return Ok(EbiInput::Usize(value.clone(), &input_type));
                                }
                            }
                            (Some(min), None) => {
                                if min > &value {
                                    error = Some(anyhow!("value should be at least {}", min));
                                } else {
                                    return Ok(EbiInput::Usize(value.clone(), &input_type));
                                }
                            }
                            (Some(min), Some(max)) => {
                                if min > &value || max < &value {
                                    error = Some(anyhow!(
                                        "value should be between {} and {}",
                                        min,
                                        max
                                    ));
                                } else {
                                    return Ok(EbiInput::Usize(value.clone(), &input_type));
                                }
                            }
                        }
                    }
                }
            }
            EbiInputType::Fraction(min, max, _) => {
                if let MultipleReader::String(string) = &reader {
                    if let Ok(value) = &string.parse::<FractionNotParsedYet>() {
                        let value: Fraction = value.try_into()?;

                        match (min, max) {
                            (None, None) => {
                                return Ok(EbiInput::Fraction(value.clone(), &input_type));
                            }
                            (None, Some(max)) => {
                                if max < &value {
                                    error = Some(anyhow!("value should be at most {}", max));
                                } else {
                                    return Ok(EbiInput::Fraction(value.clone(), &input_type));
                                }
                            }
                            (Some(min), None) => {
                                if min > &value {
                                    error = Some(anyhow!("value should be at least {}", min));
                                } else {
                                    return Ok(EbiInput::Fraction(value.clone(), &input_type));
                                }
                            }
                            (Some(min), Some(max)) => {
                                if min > &value || max < &value {
                                    error = Some(anyhow!(
                                        "value should be between {} and {}",
                                        min,
                                        max
                                    ));
                                } else {
                                    return Ok(EbiInput::Fraction(value.clone(), &input_type));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    match error {
        Some(e) => Err(e),
        None => Err(anyhow!("Argument was not given.")),
    }
}

pub fn validate_object_of(
    reader: &mut MultipleReader,
    file_handler: &EbiFileHandler,
) -> Result<()> {
    if let Some(validator) = file_handler.validator {
        let result = (validator)(reader.get()?.as_mut());
        return result;
    } else {
        return Err(anyhow!(
            "file hanlder {} does not have a validator",
            file_handler
        ));
    }
}

pub const TEST_INPUT_TYPE_FRACTION: EbiInputType = EbiInputType::Fraction(None, None, None);
pub const TEST_INPUT_TYPE_USIZE: EbiInputType = EbiInputType::Usize(None, None, None);
pub const TEST_INPUT_TYPE_STRING: EbiInputType = EbiInputType::String(None, None);

#[cfg(test)]
mod tests {
    use super::{EbiInputType, validate_object_of};
    use crate::ebi_framework::ebi_input;
    use crate::{
        ebi_file_handlers::compressed_event_log::EBI_COMPRESSED_EVENT_LOG,
        ebi_framework::{
            ebi_file_handler::EBI_FILE_HANDLERS,
            ebi_input::{EbiInput, TEST_INPUT_TYPE_FRACTION, TEST_INPUT_TYPE_USIZE},
        },
        multiple_reader::MultipleReader,
    };
    use ebi_objects::ebi_arithmetic::{Fraction, Zero};
    use std::{
        fs::{self, File},
        path::PathBuf,
    };
    use strum::IntoEnumIterator;

    #[test]
    fn input_primitives() {
        EbiInput::FileHandler(EBI_COMPRESSED_EVENT_LOG).get_type();
        EbiInput::Fraction(Fraction::zero(), &TEST_INPUT_TYPE_FRACTION).get_type();
        EbiInput::Usize(0, &TEST_INPUT_TYPE_USIZE).get_type();
    }

    #[test]
    fn input_types() {
        for input_type in EbiInputType::iter() {
            let list = vec![&input_type];
            input_type.get_article();
            EbiInputType::get_parser_of_list(&list);
            input_type.get_applicable_commands();
            input_type.to_string();
            EbiInputType::get_possible_inputs(&list);
            EbiInputType::get_possible_inputs_with_latex(&list);
            EbiInputType::possible_inputs_as_strings_with_articles(&list, "xyz");
        }
    }

    #[test]
    fn validators() {
        for file_handler in EBI_FILE_HANDLERS {
            let files = fs::read_dir("./testfiles").unwrap();
            for path in files {
                let file = path.unwrap();
                println!("file {:?}", file.file_name());

                let mut reader = MultipleReader::from_file(File::open(file.path()).unwrap());

                if file_handler.validator.is_some() {
                    println!("\tfile handler {}", file_handler);
                    if !file.file_name().into_string().unwrap().contains("invalid")
                        && file
                            .file_name()
                            .into_string()
                            .unwrap()
                            .ends_with(&(".".to_string() + file_handler.file_extension))
                    {
                        //file handler should be able to accept this file
                        assert!(validate_object_of(&mut reader, file_handler).is_ok());
                    } else {
                        //file handler should not accept this file
                        assert!(validate_object_of(&mut reader, file_handler).is_err());
                    }
                }
            }
        }
    }

    #[test]
    fn reader_file() {
        let file = "testfiles/a-b.xes".parse::<PathBuf>().unwrap();
        ebi_input::get_reader_file(&file).unwrap();
    }
}
