use std::str::FromStr;

use thiserror::Error;

use super::LogicalExpression;

/// Represents the possible errors that can occur during parsing of a logical expression.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)]
pub enum ParseError<E> {
    /// Represents an error that occurred while parsing a condition.
    #[error("Error parsing condition: {0}")]
    ConditionParsing(E),

    /// Represents an error when there is no matching opening bracket for a closing bracket.
    #[error("No matching opening bracket")]
    NoMatchingOpeningBracket,

    /// Represents an error when there is no matching closing bracket for an opening bracket.
    #[error("No matching closing bracket")]
    NoMatchingClosingBracket,

    /// Represents an error when there are multiple operators without a condition between them.
    #[error("Multiple operators without a condition between them")]
    MultipleOperators,

    /// Represents an error when there is an empty condition.
    #[error("Empty condition")]
    EmptyCondition,

    /// Represents an error when there is a leading operator without a preceding condition.
    #[error("Leading operator without a preceding condition")]
    LeadingOperator,

    /// Represents an error when there is a trailing operator without a following condition.
    #[error("Trailing operator without a following condition")]
    TrailingOperator,

    /// Represents an error when there is a condition before an opening bracket.
    #[error("Condition before an opening bracket")]
    ConditionBeforeOpeningBracket,

    /// Represents an error when there is a condition after a closing bracket.
    #[error("Condition after a closing bracket")]
    ConditionAfterClosingBracket,
}

impl<E> From<E> for ParseError<E> {
    fn from(err: E) -> Self {
        Self::ConditionParsing(err)
    }
}

struct Lists<T> {
    or: Vec<T>,
    and: Vec<T>,
}

impl<T> Lists<T> {
    const fn new() -> Self {
        Self {
            or: Vec::new(),
            and: Vec::new(),
        }
    }
}

impl<C: FromStr> LogicalExpression<C> {
    /// Parses a logical expression from a string.
    ///
    /// # Errors
    /// Returns a `ParseError` if the string is not a valid logical expression, or if parsing a condition fails.
    #[inline]
    pub fn parse(s: &str) -> Result<Self, ParseError<<C as FromStr>::Err>> {
        Self::parse_with(s, FromStr::from_str)
    }
}

impl<C> LogicalExpression<C> {
    /// Parses a logical expression from a string using a custom parsing function.
    ///
    /// The `parse_condition` function is used to parse individual conditions from string slices.
    ///
    /// # Errors
    /// Returns a `ParseError` if the string is not a valid logical expression, or if `parse_condition` fails.
    pub fn parse_with<F, E>(s: &str, mut parse_condition: F) -> Result<Self, ParseError<E>>
    where
        F: FnMut(&str) -> Result<C, E>,
    {
        Self::parse_with_expression(s, |s| Ok(Self::Condition(parse_condition(s)?)))
    }

    /// Parses a logical expression from a string using a custom parsing function.
    ///
    /// The `parse_expression` function is used to parse individual conditions from string slices into expressions.
    ///
    /// # Errors
    /// Returns a `ParseError` if the string is not a valid logical expression, or if `parse_expression` fails.
    pub fn parse_with_expression<F, E>(
        s: &str,
        mut parse_expression: F,
    ) -> Result<Self, ParseError<E>>
    where
        F: FnMut(&str) -> Result<Self, E>,
    {
        enum After {
            Start,
            Operator,
        }

        let mut bracket_stack = Vec::new();
        let mut lists = Lists::new();
        let mut start = 0;
        let mut end = 0;

        let mut state = Some(After::Start);

        for c in s.chars() {
            let clen = c.len_utf8();
            match c {
                '|' | '&' => {
                    let condition = s[start..end].trim();
                    if let Some(after) = state {
                        if condition.is_empty() {
                            return Err(match after {
                                After::Start => ParseError::LeadingOperator,
                                After::Operator => ParseError::MultipleOperators,
                            });
                        }
                        lists.and.push(parse_expression(condition)?);
                    } else if !condition.is_empty() {
                        return Err(ParseError::ConditionAfterClosingBracket);
                    }

                    start = end + clen;

                    if c == '|' {
                        lists.or.push(Self::and(lists.and));
                        lists.and = Vec::new();
                    }

                    state = Some(After::Operator);
                }
                '(' => {
                    let condition = s[start..end].trim();
                    if !condition.is_empty() {
                        return Err(ParseError::ConditionBeforeOpeningBracket);
                    }

                    bracket_stack.push(lists);
                    lists = Lists::new();

                    start = end + clen;

                    state = Some(After::Start);
                }
                ')' => {
                    let Some(mut stack_lists) = bracket_stack.pop() else {
                        return Err(ParseError::NoMatchingOpeningBracket);
                    };

                    let condition = s[start..end].trim();
                    if let Some(after) = state {
                        if condition.is_empty() {
                            return Err(match after {
                                After::Start => ParseError::EmptyCondition,
                                After::Operator => ParseError::TrailingOperator,
                            });
                        }
                        lists.and.push(parse_expression(condition)?);
                    } else if !condition.is_empty() {
                        return Err(ParseError::ConditionAfterClosingBracket);
                    }

                    start = end + clen;

                    lists.or.push(Self::and(lists.and));

                    stack_lists.and.push(Self::or(lists.or));

                    lists = stack_lists;

                    state = None;
                }
                _ => (),
            }
            end += clen;
        }

        if !bracket_stack.is_empty() {
            return Err(ParseError::NoMatchingClosingBracket);
        }

        let condition = s[start..end].trim();
        if let Some(after) = state {
            if condition.is_empty() {
                return Err(match after {
                    After::Start => ParseError::EmptyCondition,
                    After::Operator => ParseError::TrailingOperator,
                });
            }
            lists.and.push(parse_expression(condition)?);
        } else if !condition.is_empty() {
            return Err(ParseError::ConditionAfterClosingBracket);
        }

        lists.or.push(Self::and(lists.and));

        Ok(Self::or(lists.or))
    }
}
