use crate::SingleLineMatch;
use crate::{Match, Origin, Problem};
use regex::{Captures, Regex};
use std::fmt::Display;

#[derive(Debug)]
pub struct Error {
    pub message: String,
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.message.fmt(f)
    }
}

impl std::error::Error for Error {}

pub struct RegexLineMatcher {
    regex: Regex,
    callback: Box<dyn Fn(&Captures) -> Result<Option<Box<dyn Problem>>, Error> + Send + Sync>,
}

pub trait Matcher: Sync {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error>;
}

impl RegexLineMatcher {
    pub fn new(
        regex: Regex,
        callback: Box<dyn Fn(&Captures) -> Result<Option<Box<dyn Problem>>, Error> + Send + Sync>,
    ) -> Self {
        Self { regex, callback }
    }

    pub fn matches_line(&self, line: &str) -> bool {
        self.regex.is_match(line)
    }

    pub fn extract_from_line(&self, line: &str) -> Result<Option<Option<Box<dyn Problem>>>, Error> {
        let c = self.regex.captures(line);
        if let Some(c) = c {
            return Ok(Some((self.callback)(&c)?));
        }
        Ok(None)
    }

    fn origin(&self) -> Origin {
        Origin(format!("direct regex ({})", self.regex.as_str()))
    }
}

impl Matcher for RegexLineMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        let line = lines[offset];
        if let Some(problem) = self.extract_from_line(line)? {
            let m = SingleLineMatch {
                offset,
                line: line.to_string(),
                origin: self.origin(),
            };
            return Ok(Some((Box::new(m), problem)));
        }
        Ok(None)
    }
}

#[macro_export]
macro_rules! regex_line_matcher {
    ($regex:expr, $callback:expr) => {
        Box::new(RegexLineMatcher::new(
            regex::Regex::new($regex).unwrap(),
            Box::new($callback),
        ))
    };
    ($regex: expr) => {
        Box::new(RegexLineMatcher::new(
            regex::Regex::new($regex).unwrap(),
            Box::new(|_| Ok(None)),
        ))
    };
}

#[macro_export]
macro_rules! regex_para_matcher {
    ($regex:expr, $callback:expr) => {{
        Box::new(RegexLineMatcher::new(
            regex::Regex::new(concat!("(?s)", $regex)).unwrap(),
            Box::new($callback),
        ))
    }};
    ($regex: expr) => {{
        Box::new(RegexLineMatcher::new(
            regex::Regex::new(concat!("(?s)", $regex)).unwrap(),
            Box::new(|_| Ok(None)),
        ))
    }};
}

pub struct MatcherGroup(Vec<Box<dyn Matcher>>);

impl MatcherGroup {
    pub fn new(matchers: Vec<Box<dyn Matcher>>) -> Self {
        Self(matchers)
    }
}

impl Default for MatcherGroup {
    fn default() -> Self {
        Self::new(vec![])
    }
}

impl From<Vec<Box<dyn Matcher>>> for MatcherGroup {
    fn from(matchers: Vec<Box<dyn Matcher>>) -> Self {
        Self::new(matchers)
    }
}

impl MatcherGroup {
    pub fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        for matcher in self.0.iter() {
            if let Some(p) = matcher.extract_from_lines(lines, offset)? {
                return Ok(Some(p));
            }
        }
        Ok(None)
    }
}
