//! Filter expression language.
//!
//! This module contains methods to parse a given string into an abstract filter tree, check its
//! type and convert it to a [`Filter`][super::filters::Filter].
// Make it available in the grammar mod.
use super::{filters, FightOutcome, SearchField, Weekday};

use std::{error, fmt};

use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
use thiserror::Error;

lalrpop_mod!(#[allow(clippy::all)] pub grammar, "/fexpr/grammar.rs");

#[derive(Debug)]
pub struct FError {
    location: usize,
    data: String,
    kind: FErrorKind,
}

impl fmt::Display for FError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} (at {})", self.kind, self.location)
    }
}

impl error::Error for FError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        Some(&self.kind)
    }
}

#[derive(Debug, Error)]
pub enum FErrorKind {
    #[error("invalid regular expression: {0}")]
    InvalidRegex(#[from] regex::Error),
    #[error("invalid fight outcome")]
    InvalidFightOutcome,
    #[error("invalid weekday")]
    InvalidWeekday,
    #[error("invalid timestamp: {0}")]
    InvalidTimestamp(#[from] chrono::format::ParseError),
    #[error("invalid boss name")]
    InvalidBoss,
}

/// Shortcut to create a new parser and parse the given input.
pub fn parse_logfilter<'a>(
    input: &'a str,
) -> Result<Box<dyn filters::log::LogFilter>, ParseError<usize, Token<'a>, FError>> {
    grammar::LogFilterParser::new().parse(input)
}

/// Extract the location from the given error.
pub fn location<T>(err: &ParseError<usize, T, FError>) -> usize {
    match *err {
        ParseError::InvalidToken { location } => location,
        ParseError::UnrecognizedEOF { location, .. } => location,
        ParseError::UnrecognizedToken {
            token: (l, _, _), ..
        } => l,
        ParseError::ExtraToken { token: (l, _, _) } => l,
        ParseError::User { ref error } => error.location,
    }
}