use super::super::Suggestion;
use super::super::expr_linter::ExprLinter;
use crate::expr::Expr;
use crate::expr::SequenceExpr;
use crate::linting::LintKind;
use crate::linting::expr_linter::Chunk;
use crate::patterns::WordSet;
use crate::{Lint, Lrc, Token};

/// Linter that checks if `criteria` or `phenomena` is used as singular.
pub struct SingularCriteriaPhenomena {
    expr: SequenceExpr,
    plural_words: Lrc<WordSet>,
    singular_modifiers: Lrc<WordSet>,
}

impl SingularCriteriaPhenomena {
    fn new() -> Self {
        let plural_words = Lrc::new(WordSet::new(&["criteria", "phenomena"]));

        let singular_modifiers = Lrc::new(WordSet::new(&["this", "that", "a", "one"]));

        Self {
            expr: SequenceExpr::with(singular_modifiers.clone())
                .then_whitespace()
                .then(plural_words.clone()),
            plural_words,
            singular_modifiers,
        }
    }
}

impl ExprLinter for SingularCriteriaPhenomena {
    type Unit = Chunk;

    fn expr(&self) -> &dyn Expr {
        &self.expr
    }

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let mut second_word: String = matched_tokens[2]
            .span
            .get_content(source)
            .iter()
            .copied()
            .collect();
        second_word.make_ascii_lowercase();

        let suggestions = match second_word.as_str() {
            "criteria" => vec![Suggestion::ReplaceWith("criterion".chars().collect())],
            "phenomena" => vec![Suggestion::ReplaceWith("phenomenon".chars().collect())],
            _ => panic!("Don't know what to say about '{second_word}'!"),
        };

        Some(Lint {
            span: matched_tokens[2].span,
            lint_kind: LintKind::Repetition,
            message: "You used a plural noun as singular.".to_owned(),
            priority: 63,
            suggestions,
        })
    }

    fn description(&self) -> &'static str {
        "The words “criteria” and “phenomena” are the plurals of “criterion” and “phenomenon”, respectively. They are often incorrectly used as singular."
    }
}

impl Default for SingularCriteriaPhenomena {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::SingularCriteriaPhenomena;
    use crate::linting::tests::{assert_lint_count, assert_suggestion_result};

    #[test]
    fn can_detect_incorrect_criteria() {
        assert_suggestion_result(
            "...One criteria is essential...",
            SingularCriteriaPhenomena::default(),
            "...One criterion is essential...",
        )
    }

    #[test]
    fn can_detect_incorrect_phenomena() {
        assert_suggestion_result(
            "...I would like to see that phenomena.",
            SingularCriteriaPhenomena::default(),
            "...I would like to see that phenomenon.",
        )
    }

    #[test]
    fn allows_correct_criteria() {
        assert_lint_count(
            "...She disagrees with those criteria.",
            SingularCriteriaPhenomena::default(),
            0,
        )
    }

    #[test]
    fn allows_correct_phenomena() {
        assert_lint_count(
            "...Many phenomena were on display.",
            SingularCriteriaPhenomena::default(),
            0,
        )
    }
}
