use std::fmt;
use std::sync::{Arc, Mutex};

use crate::errors::{ConsoleErrorListener, ErrorListener, SyntaxErrorEvent};
use crate::vocabulary::Vocabulary;

#[derive(Clone)]
struct ErrorListenerSlot(Arc<Mutex<dyn for<'a> ErrorListener<dyn Recognizer + 'a> + Send>>);

impl ErrorListenerSlot {
    fn new<L>(listener: L) -> Self
    where
        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
    {
        Self(Arc::new(Mutex::new(listener)))
    }

    fn syntax_error(&self, recognizer: &(dyn Recognizer + '_), event: &SyntaxErrorEvent<'_>) {
        self.0
            .lock()
            .expect("error listener lock poisoned")
            .syntax_error(recognizer, event);
    }
}

impl fmt::Debug for ErrorListenerSlot {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("ErrorListener")
    }
}

#[derive(Clone, Debug)]
pub(crate) struct RecognizerMetadata {
    grammar_file_name: String,
    rule_names: Vec<String>,
    channel_names: Vec<String>,
    mode_names: Vec<String>,
    vocabulary: Vocabulary,
}

impl RecognizerMetadata {
    pub(crate) fn from_static(
        grammar_file_name: &'static str,
        rule_names: &'static [&'static str],
        channel_names: &'static [&'static str],
        mode_names: &'static [&'static str],
        vocabulary: Vocabulary,
    ) -> Self {
        Self {
            grammar_file_name: grammar_file_name.to_owned(),
            rule_names: rule_names.iter().map(|name| (*name).to_owned()).collect(),
            channel_names: channel_names
                .iter()
                .map(|name| (*name).to_owned())
                .collect(),
            mode_names: mode_names.iter().map(|name| (*name).to_owned()).collect(),
            vocabulary,
        }
    }
}

#[derive(Clone, Debug)]
pub struct RecognizerData {
    metadata: Arc<RecognizerMetadata>,
    state: isize,
    console_error_listener: bool,
    error_listeners: Vec<ErrorListenerSlot>,
}

impl RecognizerData {
    pub fn new(grammar_file_name: impl Into<String>, vocabulary: Vocabulary) -> Self {
        Self {
            metadata: Arc::new(RecognizerMetadata {
                grammar_file_name: grammar_file_name.into(),
                rule_names: Vec::new(),
                channel_names: Vec::new(),
                mode_names: Vec::new(),
                vocabulary,
            }),
            state: -1,
            console_error_listener: true,
            error_listeners: Vec::new(),
        }
    }

    pub(crate) const fn from_shared(metadata: Arc<RecognizerMetadata>) -> Self {
        Self {
            metadata,
            state: -1,
            console_error_listener: true,
            error_listeners: Vec::new(),
        }
    }

    #[must_use]
    pub fn with_rule_names(
        mut self,
        rule_names: impl IntoIterator<Item = impl Into<String>>,
    ) -> Self {
        Arc::make_mut(&mut self.metadata).rule_names =
            rule_names.into_iter().map(Into::into).collect();
        self
    }

    #[must_use]
    pub fn with_channel_names(
        mut self,
        channel_names: impl IntoIterator<Item = impl Into<String>>,
    ) -> Self {
        Arc::make_mut(&mut self.metadata).channel_names =
            channel_names.into_iter().map(Into::into).collect();
        self
    }

    #[must_use]
    pub fn with_mode_names(
        mut self,
        mode_names: impl IntoIterator<Item = impl Into<String>>,
    ) -> Self {
        Arc::make_mut(&mut self.metadata).mode_names =
            mode_names.into_iter().map(Into::into).collect();
        self
    }

    /// Rule names owned by this recognizer's metadata.
    ///
    /// Also available through [`Recognizer::rule_names`]; this inherent
    /// accessor lets callers that already hold a `RecognizerData` field
    /// borrow rule names without borrowing the whole recognizer.
    #[must_use]
    pub fn rule_names(&self) -> &[String] {
        &self.metadata.rule_names
    }

    /// The token vocabulary for literal/symbolic name resolution.
    #[must_use]
    pub fn vocabulary(&self) -> &Vocabulary {
        &self.metadata.vocabulary
    }

    pub const fn state(&self) -> isize {
        self.state
    }

    pub const fn set_state(&mut self, state: isize) {
        self.state = state;
    }

    fn add_error_listener<L>(&mut self, listener: L)
    where
        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
    {
        self.error_listeners.push(ErrorListenerSlot::new(listener));
    }

    fn remove_error_listeners(&mut self) {
        self.console_error_listener = false;
        self.error_listeners.clear();
    }

    fn notify_error_listeners(&self, recognizer: &dyn Recognizer, event: &SyntaxErrorEvent<'_>) {
        if self.console_error_listener {
            ConsoleErrorListener.syntax_error(recognizer, event);
        }
        for listener in &self.error_listeners {
            listener.syntax_error(recognizer, event);
        }
    }
}

pub trait Recognizer {
    fn data(&self) -> &RecognizerData;
    fn data_mut(&mut self) -> &mut RecognizerData;

    fn grammar_file_name(&self) -> &str {
        &self.data().metadata.grammar_file_name
    }

    fn rule_names(&self) -> &[String] {
        &self.data().metadata.rule_names
    }

    fn channel_names(&self) -> &[String] {
        &self.data().metadata.channel_names
    }

    fn mode_names(&self) -> &[String] {
        &self.data().metadata.mode_names
    }

    fn vocabulary(&self) -> &Vocabulary {
        &self.data().metadata.vocabulary
    }

    fn state(&self) -> isize {
        self.data().state()
    }

    fn set_state(&mut self, state: isize) {
        self.data_mut().set_state(state);
    }

    /// Adds a listener for syntax and prediction diagnostics.
    ///
    /// Recognizers start with one [`ConsoleErrorListener`]. Call
    /// [`Self::remove_error_listeners`] before adding a replacement when
    /// diagnostics should not also be written to stderr.
    fn add_error_listener<L>(&mut self, listener: L)
    where
        Self: Sized,
        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
    {
        self.data_mut().add_error_listener(listener);
    }

    /// Removes every error listener, including the default console listener.
    fn remove_error_listeners(&mut self) {
        self.data_mut().remove_error_listeners();
    }

    /// Sends one diagnostic to every registered error listener.
    fn notify_error_listeners(&self, event: SyntaxErrorEvent<'_>)
    where
        Self: Sized,
    {
        self.data().notify_error_listeners(self, &event);
    }

    fn sempred(&mut self, _rule_index: usize, _pred_index: usize) -> bool {
        true
    }

    fn action(&mut self, _rule_index: usize, _action_index: usize) {}
}

#[cfg(test)]
#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
mod tests {
    use std::mem::size_of;

    use super::*;
    use crate::errors::AntlrError;
    use crate::generated::GrammarMetadata;

    static SHARED_METADATA: GrammarMetadata = GrammarMetadata::new(
        "Shared.g4",
        &["start"],
        &[None, Some("'x'")],
        &[None, Some("X")],
        &[None, None],
        &["DEFAULT_TOKEN_CHANNEL", "HIDDEN"],
        &["DEFAULT_MODE"],
        &[],
    );

    #[derive(Clone, Debug, Eq, PartialEq)]
    struct RecordedError {
        grammar_file_name: String,
        offending_text: Option<String>,
        line: usize,
        column: usize,
        span: Option<std::ops::Range<usize>>,
        message: String,
        error: Option<AntlrError>,
    }

    #[derive(Clone, Debug)]
    struct RecordingErrorListener {
        errors: Arc<Mutex<Vec<RecordedError>>>,
    }

    impl<R> ErrorListener<R> for RecordingErrorListener
    where
        R: Recognizer + ?Sized,
    {
        fn syntax_error(&mut self, recognizer: &R, event: &SyntaxErrorEvent<'_>) {
            self.errors
                .lock()
                .expect("recorded errors lock")
                .push(RecordedError {
                    grammar_file_name: recognizer.grammar_file_name().to_owned(),
                    offending_text: event
                        .offending
                        .and_then(|token| token.text().map(str::to_owned)),
                    line: event.line,
                    column: event.column,
                    span: event.span.clone(),
                    message: event.message.to_owned(),
                    error: event.error.cloned(),
                });
        }
    }

    #[derive(Clone, Debug)]
    struct TestRecognizer {
        data: RecognizerData,
    }

    impl Recognizer for TestRecognizer {
        fn data(&self) -> &RecognizerData {
            &self.data
        }

        fn data_mut(&mut self) -> &mut RecognizerData {
            &mut self.data
        }
    }

    fn test_recognizer() -> TestRecognizer {
        TestRecognizer {
            data: RecognizerData::new(
                "Test.g4",
                Vocabulary::new(
                    std::iter::empty::<Option<&str>>(),
                    std::iter::empty::<Option<&str>>(),
                    std::iter::empty::<Option<&str>>(),
                ),
            ),
        }
    }

    #[test]
    fn recognizers_replace_the_default_console_error_listener() {
        let mut recognizer = test_recognizer();
        assert!(recognizer.data.console_error_listener);
        assert!(recognizer.data.error_listeners.is_empty());

        recognizer.remove_error_listeners();
        assert!(!recognizer.data.console_error_listener);
        assert!(recognizer.data.error_listeners.is_empty());

        let errors = Arc::new(Mutex::new(Vec::new()));
        recognizer.add_error_listener(RecordingErrorListener {
            errors: Arc::clone(&errors),
        });
        let error = AntlrError::ParserError {
            line: 3,
            column: 5,
            message: "unexpected token".to_owned(),
            offending: None,
        };
        recognizer.notify_error_listeners(SyntaxErrorEvent {
            offending: None,
            line: 3,
            column: 5,
            span: Some(17..27),
            message: "unexpected token",
            error: Some(&error),
        });

        insta::assert_debug_snapshot!(
            "recognizers_replace_the_default_console_error_listener",
            *errors.lock().expect("recorded errors lock")
        );
    }

    #[test]
    fn recognizer_data_remains_send_and_sync() {
        fn assert_send_and_sync<T: Send + Sync>() {}

        assert_send_and_sync::<RecognizerData>();
    }

    #[test]
    fn recognizer_data_keeps_shared_metadata_out_of_line() {
        assert!(size_of::<RecognizerData>() < size_of::<RecognizerMetadata>());
    }

    #[test]
    fn cloned_recognizers_can_reconfigure_their_listener_lists_independently() {
        let mut original = test_recognizer();
        let clone = original.clone();

        original.remove_error_listeners();

        assert!(!original.data.console_error_listener);
        assert!(original.data.error_listeners.is_empty());
        assert!(clone.data.console_error_listener);
        assert!(clone.data.error_listeners.is_empty());
    }

    #[test]
    fn generated_recognizers_share_metadata_but_not_instance_state() {
        let mut first = SHARED_METADATA.recognizer_data();
        let second = SHARED_METADATA.recognizer_data();

        assert!(std::ptr::eq(first.rule_names(), second.rule_names()));
        assert!(std::ptr::eq(first.vocabulary(), second.vocabulary()));
        assert!(first.console_error_listener);
        assert!(second.console_error_listener);

        first.set_state(7);
        first.remove_error_listeners();

        assert_eq!(first.state(), 7);
        assert_eq!(second.state(), -1);
        assert!(!first.console_error_listener);
        assert!(first.error_listeners.is_empty());
        assert!(second.console_error_listener);
        assert!(second.error_listeners.is_empty());
    }

    #[test]
    fn customizing_shared_metadata_detaches_only_that_recognizer() {
        let customized = SHARED_METADATA
            .recognizer_data()
            .with_rule_names(["replacement"]);
        let shared = SHARED_METADATA.recognizer_data();

        assert_eq!(customized.rule_names(), ["replacement"]);
        assert_eq!(shared.rule_names(), ["start"]);
        assert!(!std::ptr::eq(customized.rule_names(), shared.rule_names()));
    }
}
