//
// jja: swiss army knife for chess file formats
// src/pgnfilt.rs: Portable Game Notation header filtering
//
// Copyright (c) 2023 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{
    collections::HashMap,
    error::Error,
    fmt::{self, Display, Formatter, Result as FmtResult},
    str::FromStr,
};

use console::style;
use regex::RegexBuilder;

use crate::tr;

/// Represents the various fields of a chess game that can be used for filtering.
///
/// This enumeration lists the different game fields that can be involved in filtering
/// expressions. Each variant corresponds to a specific field in a game, such as player
/// names, ratings, or the opening played.
///
/// The Field enumeration can be used to build complex filter expressions, allowing
/// the selection of games based on the values of these fields.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Field {
    /// The name of the event or tournament where the game was played.
    Event,
    /// The location where the game was played.
    Site,
    /// The date the game was played.
    Date,
    /// The round number in a tournament.
    Round,
    /// The name of the player playing with the black pieces.
    Black,
    /// The name of the player playing with the white pieces.
    White,
    /// The result of the game (e.g., 1-0, 0-1, or 1/2-1/2).
    Result,
    /// The Elo rating of the black player.
    BlackElo,
    /// The Elo rating of the white player.
    WhiteElo,
    /// The rating difference of the black player after the game.
    BlackRatingDiff,
    /// The rating difference of the white player after the game.
    WhiteRatingDiff,
    /// The title held by the black player (e.g., GM, IM, FM).
    BlackTitle,
    /// The title held by the white player (e.g., GM, IM, FM).
    WhiteTitle,
    /// The Encyclopedia of Chess Openings code for the game's opening.
    ECO,
    /// The name of the opening played in the game.
    Opening,
    /// The time control used for the game.
    TimeControl,
    /// The manner in which the game was terminated (e.g., checkmate, draw by agreement).
    Termination,
    /// Total number of plies in the game.
    TotalPlyCount,
    /// Game flags added by the SCID software (DWBMENPTKQ!?U123456)
    ScidFlags,
}

/// Represents the various operators used for comparing field values in filter expressions.
///
/// This enumeration lists the different operators that can be used in filtering expressions.
/// Each variant corresponds to a specific comparison operation between a game field and a value.
///
/// The Operator enumeration can be used to build complex filter expressions, allowing
/// the selection of games based on the comparison of field values with specific criteria.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Operator {
    /// Equals. The field value is equal to the specified value.
    Eq,
    /// Not Equals. The field value is not equal to the specified value.
    Ne,
    /// Less Than. The field value is less than the specified value.
    Lt,
    /// Less or Equal. The field value is less than or equal to the specified value.
    Le,
    /// Greater Than. The field value is greater than the specified value.
    Gt,
    /// Greater or Equal. The field value is greater than or equal to the specified value.
    Ge,
    /// Regex. The field value matches the specified regular expression pattern.
    Re,
    /// Negated Regex. The field value doesn't match the specified regular expression pattern.
    ReNeg,
}

/// Represents a filter expression used to select games based on specific field values.
///
/// A `FilterExpression` is a struct that defines a single condition for filtering games.
/// It combines a game field, an operator, and a value to create an expression that can be
/// used to determine whether a game matches the specified criteria.
///
/// Filter expressions can be combined using logical operators (such as `AND` and `OR`) to create
/// more complex filters, allowing for fine-grained selection of games based on multiple
/// conditions.
///
/// # Fields
///
/// - `field: Field`: The game field to be compared (e.g., `WhiteElo`, `Result`, `Date`, etc.).
/// - `operator: Operator`: The comparison operator to be used (e.g., `Eq`, `Gt`, `Re`, etc.).
/// - `value: String`: The value to be compared against the game field's value.
/// - `regex: Option<Regex>`: The compiled regex expression for `Operator::Re`, `None` otherwise.
#[derive(Clone, Debug)]
pub struct FilterExpression {
    field: Field,
    operator: Operator,
    value: String,
    regex: Option<regex::Regex>,
}

/// Represents a logical operator used to combine filter expressions when filtering games.
///
/// A `LogicalOperator` is an enum that defines the logical operators `AND` and `OR`, which can be
/// used to combine filter expressions when selecting games based on multiple conditions.
///
/// By using logical operators, it is possible to create complex filters that can select games
/// based on various combinations of field values.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum LogicalOperator {
    /// Represents the logical AND operator. When used between two filter expressions,
    /// both expressions must be true for a game to match the filter.
    And,
    /// Represents the logical OR operator. When used between two filter expressions,
    /// at least one of the expressions must be true for a game to match the filter.
    Or,
}

/// Represents a component of a filter used for selecting games based on field values.
///
/// A `FilterComponent` is an enum that defines the different parts of a filter expression.
/// It can be one of the following:
///
/// - A `FilterExpression`, which consists of a field, an operator, and a value.
/// - A `LogicalOperator`, which is used to combine filter expressions.
/// - An `OpenParenthesis` or `CloseParenthesis`, which are used to group filter expressions
/// and control the precedence of logical operators.
///
/// By combining these components, complex filter expressions can be constructed to select
/// games based on multiple conditions and grouping.
#[derive(Clone, Debug, PartialEq)]
pub enum FilterComponent {
    /// Represents a filter expression with a field, operator, and value.
    Expression(FilterExpression),
    /// Represents a logical operator used to combine filter expressions.
    LogicalOperator(LogicalOperator),
    /// Represents an open parenthesis used to group filter expressions.
    OpenParenthesis,
    /// Represents a close parenthesis used to group filter expressions.
    CloseParenthesis,
}

// Implement FromStr for Field
impl FromStr for Field {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Event" => Ok(Field::Event),
            "Site" => Ok(Field::Site),
            "Date" => Ok(Field::Date),
            "UTCDate" => Ok(Field::Date),
            "Round" => Ok(Field::Round),
            "White" => Ok(Field::White),
            "Black" => Ok(Field::Black),
            "Result" => Ok(Field::Result),
            "BlackElo" => Ok(Field::BlackElo),
            "WhiteElo" => Ok(Field::WhiteElo),
            "BlackRatingDiff" => Ok(Field::BlackRatingDiff),
            "WhiteRatingDiff" => Ok(Field::WhiteRatingDiff),
            "BlackTitle" => Ok(Field::BlackTitle),
            "WhiteTitle" => Ok(Field::WhiteTitle),
            "ECO" => Ok(Field::ECO),
            "Opening" => Ok(Field::Opening),
            "TimeControl" => Ok(Field::TimeControl),
            "Termination" => Ok(Field::Termination),
            "TotalPlyCount" => Ok(Field::TotalPlyCount),
            "ScidFlags" => Ok(Field::ScidFlags),
            _ => Err(()),
        }
    }
}

// Implement FromStr for Operator
impl FromStr for Operator {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "=" => Ok(Operator::Eq),
            "!=" => Ok(Operator::Ne),
            "<" => Ok(Operator::Lt),
            "<=" => Ok(Operator::Le),
            ">" => Ok(Operator::Gt),
            ">=" => Ok(Operator::Ge),
            "=~" => Ok(Operator::Re),
            "!~" => Ok(Operator::ReNeg),
            _ => Err(()),
        }
    }
}

impl fmt::Display for Operator {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let operator_str = match self {
            Operator::Eq => "=",
            Operator::Ne => "!=",
            Operator::Lt => "<",
            Operator::Le => "<=",
            Operator::Gt => ">",
            Operator::Ge => ">=",
            Operator::Re => "=~",
            Operator::ReNeg => "!~",
        };
        write!(f, "{}", operator_str)
    }
}

impl fmt::Display for LogicalOperator {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let operator_str = match self {
            LogicalOperator::And => "AND",
            LogicalOperator::Or => "OR",
        };
        write!(f, "{}", operator_str)
    }
}

impl fmt::Display for Field {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let field_str = match self {
            Field::Event => "Event",
            Field::Site => "Site",
            Field::Date => "Date",
            Field::Round => "Round",
            Field::Black => "Black",
            Field::White => "White",
            Field::Result => "Result",
            Field::BlackElo => "BlackElo",
            Field::WhiteElo => "WhiteElo",
            Field::BlackRatingDiff => "BlackRatingDiff",
            Field::WhiteRatingDiff => "WhiteRatingDiff",
            Field::BlackTitle => "BlackTitle",
            Field::WhiteTitle => "WhiteTitle",
            Field::ECO => "ECO",
            Field::Opening => "Opening",
            Field::TimeControl => "TimeControl",
            Field::Termination => "Termination",
            Field::TotalPlyCount => "TotalPlyCount",
            Field::ScidFlags => "ScidFlags",
        };
        write!(f, "{}", field_str)
    }
}

impl Field {
    /// Construct a Field variant from a UTF-8 encoded input.
    pub fn from_utf8(input: &[u8]) -> Option<Self> {
        match input {
            b"Event" => Some(Field::Event),
            b"Site" => Some(Field::Site),
            b"Date" => Some(Field::Date),
            b"UTCDate" => Some(Field::Date),
            b"Round" => Some(Field::Round),
            b"Black" => Some(Field::Black),
            b"White" => Some(Field::White),
            b"Result" => Some(Field::Result),
            b"BlackElo" => Some(Field::BlackElo),
            b"WhiteElo" => Some(Field::WhiteElo),
            b"BlackRatingDiff" => Some(Field::BlackRatingDiff),
            b"WhiteRatingDiff" => Some(Field::WhiteRatingDiff),
            b"BlackTitle" => Some(Field::BlackTitle),
            b"WhiteTitle" => Some(Field::WhiteTitle),
            b"ECO" => Some(Field::ECO),
            b"Opening" => Some(Field::Opening),
            b"TimeControl" => Some(Field::TimeControl),
            b"Termination" => Some(Field::Termination),
            b"TotalPlyCount" => Some(Field::TotalPlyCount),
            b"ScidFlags" => Some(Field::ScidFlags),
            _ => None,
        }
    }
}

impl PartialEq for FilterExpression {
    fn eq(&self, other: &Self) -> bool {
        self.field == other.field && self.operator == other.operator && self.value == other.value
        // Ignore the regex field when comparing for equality
    }
}

/// Represents errors that can occur during the parsing of a filter expression.
///
/// A `FilterExpressionParseError` is an enum that defines the different types of errors that can
/// occur when parsing a filter expression string. These errors include unsupported operators,
/// invalid syntax, empty expressions, invalid fields, missing operators, invalid operators, and
/// missing values.
#[derive(Debug)]
pub enum FilterExpressionParseError {
    /// Represents an unsupported operator found in the filter expression.
    UnsupportedOperator(String),
    /// Represents a general syntax error encountered in the filter expression.
    InvalidSyntax(String),
    /// Represents an empty filter expression.
    EmptyExpression,
    /// Represents an invalid field found in the filter expression.
    InvalidField(String),
    /// Represents a missing operator in the filter expression.
    MissingOperator,
    /// Represents an invalid operator found in the filter expression.
    InvalidOperator(String),
    /// Represents a missing value for a field in the filter expression.
    MissingValue,
    /// Represents an invalid value given as regular expression pattern.
    InvalidRegex(String),
}

impl Display for FilterExpressionParseError {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match self {
            FilterExpressionParseError::UnsupportedOperator(op) => {
                write!(f, "{}", tr!("Unsupported operator: `{}'", op))
            }
            FilterExpressionParseError::InvalidSyntax(expr) => {
                write!(
                    f,
                    "{}",
                    tr!("Invalid syntax in filter expression: `{}'", expr)
                )
            }
            FilterExpressionParseError::EmptyExpression => {
                write!(f, "{}", tr!("Filter expression cannot be empty"))
            }
            FilterExpressionParseError::InvalidField(field) => {
                write!(
                    f,
                    "{}",
                    tr!("Invalid field in filter expression: `{}'", field)
                )
            }
            FilterExpressionParseError::MissingOperator => {
                write!(f, "{}", tr!("Missing operator"))
            }
            FilterExpressionParseError::InvalidOperator(op) => {
                write!(
                    f,
                    "{}",
                    tr!("Invalid operator in field expression: `{}'", op)
                )
            }
            FilterExpressionParseError::MissingValue => {
                write!(f, "{}", tr!("Missing value"))
            }
            FilterExpressionParseError::InvalidRegex(regex) => {
                write!(
                    f,
                    "{}",
                    tr!("Invalid regex in field expression: `{}'", regex)
                )
            }
        }
    }
}

impl Error for FilterExpressionParseError {}

impl From<()> for FilterExpressionParseError {
    fn from(_: ()) -> Self {
        FilterExpressionParseError::InvalidField(
            tr!("Player, Elo, Title or RatingDiff expected").to_string(),
        )
    }
}

/// Parses a filter expression string into a vector of `FilterExpression` instances.
///
/// The filter expression string should contain filter conditions, which consist of
/// a tag name, a comparison operator, and a value. The following operators are supported:
/// - '>'  (greater than)
/// - '>=' (greater than or equal to)
/// - '<'  (less than)
/// - '<=' (less than or equal to)
/// - '='  (equal to)
/// - '!=' (not equal to)
/// - '=~' (regex match)
/// - '!~' (negated regex match)
///
/// Filter conditions can be combined using the following logical operators:
/// - 'AND' (logical AND)
/// - 'OR'  (logical OR)
///
/// The function returns a `Vec<FilterComponent>` that represents the parsed filter conditions
/// and logical operators in the order they appear in the input string.
///
/// # Errors
///
/// This function may return a `FilterExpressionParseError` if the input filter expression
/// is not well-formed or contains unsupported operators.
///
/// # Arguments
///
/// * `filter_expr` - A filter expression string containing filter conditions and logical operators.
///
/// # Returns
///
/// A vector of `FilterComponent` instances representing the parsed filter conditions and logical operators.
pub fn parse_filter_expression(
    filter_expr: &str,
) -> Result<Vec<FilterComponent>, FilterExpressionParseError> {
    let mut components = Vec::new();
    let mut tokens = filter_expr.split_whitespace();
    let mut current_token = tokens.next();

    while let Some(token) = current_token {
        match token {
            "(" => {
                components.push(FilterComponent::OpenParenthesis);
            }
            ")" => {
                components.push(FilterComponent::CloseParenthesis);
            }
            "AND" | "OR" => {
                let logical_operator = match token {
                    "AND" => LogicalOperator::And,
                    "OR" => LogicalOperator::Or,
                    _ => unreachable!(),
                };
                components.push(FilterComponent::LogicalOperator(logical_operator));
            }
            _ => {
                let field_str = token;
                let operator_str = tokens
                    .next()
                    .ok_or(FilterExpressionParseError::MissingOperator)?;
                let operator = match Operator::from_str(operator_str) {
                    Ok(operator) => operator,
                    Err(_) => {
                        return Err(FilterExpressionParseError::InvalidOperator(
                            operator_str.to_string(),
                        ))
                    }
                };

                let value = tokens
                    .next()
                    .ok_or(FilterExpressionParseError::MissingValue)?;

                if field_str == "Player"
                    || field_str == "Elo"
                    || field_str == "Title"
                    || field_str == "RatingDiff"
                {
                    let player_dependent_expression =
                        build_player_dependent_expression(field_str, operator, value)?;
                    components.extend(player_dependent_expression);
                } else {
                    let field = match Field::from_str(field_str) {
                        Ok(field) => field,
                        Err(_) => {
                            return Err(FilterExpressionParseError::InvalidField(
                                field_str.to_string(),
                            ))
                        }
                    };

                    let regex = if operator == Operator::Re || operator == Operator::ReNeg {
                        match RegexBuilder::new(value).case_insensitive(true).build() {
                            Ok(re) => Some(re),
                            Err(err) => {
                                eprintln!(
                                    "Invalid regex in filter expression `{}': {}",
                                    &value, err
                                );
                                return Err(FilterExpressionParseError::InvalidRegex(
                                    value.to_string(),
                                ));
                            }
                        }
                    } else {
                        None
                    };

                    let expression = FilterExpression {
                        field,
                        operator,
                        value: value.to_string(),
                        regex,
                    };
                    components.push(FilterComponent::Expression(expression));
                }
            }
        }

        current_token = tokens.next();
    }

    Ok(components)
}

/// Determines if a game passes the filter conditions specified by a list of filter expressions.
///
/// `filter_expr` is a list of filter expressions to be applied to the game tags.
/// `game_tags` is a hashmap of the game's tags (e.g., "White", "Black", "Result", etc.) and their respective values.
///
/// This function checks if the game, represented by its `game_tags`, satisfies all filter conditions specified in `filter_expr`.
/// If the game passes all filter conditions, the function returns `true`; otherwise, it returns `false`.
pub fn game_passes_filter(
    filter_expr: &[FilterComponent],
    game_tags: &HashMap<Field, String>,
    verbose: u8,
) -> bool {
    fn evaluate_expression(
        filter_expr: &[FilterComponent],
        game_tags: &HashMap<Field, String>,
        index: &mut usize,
        verbose: u8,
    ) -> bool {
        let mut result = true;
        let mut current_operator = None;

        while *index < filter_expr.len() {
            match &filter_expr[*index] {
                FilterComponent::OpenParenthesis => {
                    *index += 1;
                    let inner_result = evaluate_expression(filter_expr, game_tags, index, verbose);

                    result = match current_operator {
                        Some(LogicalOperator::And) => result && inner_result,
                        Some(LogicalOperator::Or) => result || inner_result,
                        None => inner_result,
                    };
                    current_operator = None;
                }
                FilterComponent::CloseParenthesis => {
                    *index += 1;
                    break;
                }
                FilterComponent::Expression(FilterExpression {
                    field,
                    operator,
                    value,
                    regex,
                }) => {
                    let tag_value = game_tags.get(field);
                    let comparison_result = match tag_value {
                        Some(tag_value) => compare_tag_value(tag_value, operator, value, regex),
                        None => false,
                    };

                    result = match current_operator {
                        Some(LogicalOperator::And) => result && comparison_result,
                        Some(LogicalOperator::Or) => result || comparison_result,
                        None => comparison_result,
                    };

                    if verbose > 1 && result {
                        // Conditionally print debug information
                        eprintln!(
                            "{}",
                            tr!(
                            "Field: {}, Operator: {}, LogicalOperator: {} Value: {}, Tag Value: {}",
                            style(field).magenta(),
                            style(operator).bold().cyan(),
                            style(current_operator.map_or("None".to_string(), |v| v.to_string()))
                                .bold()
                                .yellow(),
                            style(value).green(),
                            tag_value.map_or(style("?").bold().red().to_string(), |v| style(v)
                                .bold()
                                .green()
                                .to_string()))
                        );
                    }

                    *index += 1;
                    current_operator = None;
                }
                FilterComponent::LogicalOperator(operator) => {
                    current_operator = Some(*operator);
                    *index += 1;
                }
            }
        }

        result
    }

    let mut index = 0;
    evaluate_expression(filter_expr, game_tags, &mut index, verbose)
}

fn build_player_dependent_expression(
    field: &str,
    operator: Operator,
    value: &str,
) -> Result<Vec<FilterComponent>, FilterExpressionParseError> {
    let field = if field == "Player" { "" } else { field };
    let white_field = Field::from_str(&format!("White{}", field))?;
    let black_field = Field::from_str(&format!("Black{}", field))?;

    let operator_str = operator.to_string();
    let expression = format!(
        "( {} {} {} OR {} {} {} )",
        white_field, operator_str, value, black_field, operator_str, value
    );

    parse_filter_expression(&expression)
}

fn compare_tag_value(
    tag_value: &str,
    operator: &Operator,
    value: &str,
    regex: &Option<regex::Regex>,
) -> bool {
    match operator {
        Operator::Eq => tag_value == value,
        Operator::Ne => tag_value != value,
        Operator::Lt => match (tag_value.parse::<i32>(), value.parse::<i32>()) {
            (Ok(tag_val_num), Ok(value_num)) => tag_val_num < value_num,
            _ => false,
        },
        Operator::Le => match (tag_value.parse::<i32>(), value.parse::<i32>()) {
            (Ok(tag_val_num), Ok(value_num)) => tag_val_num <= value_num,
            _ => false,
        },
        Operator::Gt => match (tag_value.parse::<i32>(), value.parse::<i32>()) {
            (Ok(tag_val_num), Ok(value_num)) => tag_val_num > value_num,
            _ => false,
        },
        Operator::Ge => match (tag_value.parse::<i32>(), value.parse::<i32>()) {
            (Ok(tag_val_num), Ok(value_num)) => tag_val_num >= value_num,
            _ => false,
        },
        Operator::Re => {
            if let Some(re) = regex {
                re.is_match(tag_value)
            } else {
                eprintln!(
                    "{}",
                    tr!(
                        "Error: regex is missing in filter expression for `{}'",
                        tag_value
                    )
                );
                false
            }
        }
        Operator::ReNeg => {
            if let Some(re) = regex {
                !re.is_match(tag_value)
            } else {
                eprintln!(
                    "{}",
                    tr!(
                        "Error: regex is missing in filter expression for `{}'",
                        tag_value
                    )
                );
                false
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use regex::Regex;

    use super::*;

    fn build_game_tags() -> HashMap<Field, String> {
        let mut game_tags = HashMap::new();
        game_tags.insert(Field::Event, "World Championship".to_string());
        game_tags.insert(Field::White, "Magnus Carlsen".to_string());
        game_tags.insert(Field::Black, "Fabiano Caruana".to_string());
        game_tags.insert(Field::Result, "1-0".to_string());
        game_tags.insert(Field::ECO, "C65".to_string());
        game_tags
    }

    #[test]
    fn test_parse_filter_expression() {
        let filter_expr = "Event =~ World AND White =~ Carlsen AND Result = 1-0 OR ECO = C65";
        let parsed_expr = parse_filter_expression(filter_expr).unwrap();

        assert_eq!(
            parsed_expr,
            vec![
                FilterComponent::Expression(FilterExpression {
                    field: Field::Event,
                    operator: Operator::Re,
                    value: "World".to_string(),
                    regex: None,
                }),
                FilterComponent::LogicalOperator(LogicalOperator::And),
                FilterComponent::Expression(FilterExpression {
                    field: Field::White,
                    operator: Operator::Re,
                    value: "Carlsen".to_string(),
                    regex: None,
                }),
                FilterComponent::LogicalOperator(LogicalOperator::And),
                FilterComponent::Expression(FilterExpression {
                    field: Field::Result,
                    operator: Operator::Eq,
                    value: "1-0".to_string(),
                    regex: None,
                }),
                FilterComponent::LogicalOperator(LogicalOperator::Or),
                FilterComponent::Expression(FilterExpression {
                    field: Field::ECO,
                    operator: Operator::Eq,
                    value: "C65".to_string(),
                    regex: None,
                }),
            ]
        );
    }

    #[test]
    fn test_game_passes_filter() {
        let filter_expr =
            "Event =~ World AND White =~ Carlsen AND ( Result = 1/2-1/2 OR ECO = C65 )";
        let parsed_expr = parse_filter_expression(filter_expr).unwrap();

        let game_tags = build_game_tags();

        assert!(game_passes_filter(&parsed_expr, &game_tags, 0));
    }

    #[test]
    fn test_game_does_not_pass_filter() {
        let filter_expr = "Event =~ World AND White =~ Carlsen AND ( Result = 0-1 OR ECO = B33 )";
        let parsed_expr = parse_filter_expression(filter_expr).unwrap();

        let game_tags = build_game_tags();

        assert!(!game_passes_filter(&parsed_expr, &game_tags, 0));
    }

    #[test]
    fn test_parse_filter_expression_player() {
        let filter_expr = "Player =~ ^M";
        let parsed = parse_filter_expression(filter_expr).unwrap();
        assert_eq!(
            parsed,
            vec![
                FilterComponent::OpenParenthesis,
                FilterComponent::Expression(FilterExpression {
                    field: Field::White,
                    operator: Operator::Re,
                    value: "^M".to_string(),
                    regex: Some(Regex::new("(?i)^M").unwrap()),
                }),
                FilterComponent::LogicalOperator(LogicalOperator::Or),
                FilterComponent::Expression(FilterExpression {
                    field: Field::Black,
                    operator: Operator::Re,
                    value: "^M".to_string(),
                    regex: Some(Regex::new("(?i)^M").unwrap()),
                }),
                FilterComponent::CloseParenthesis
            ]
        );
    }

    #[test]
    fn test_parse_filter_expression_elo() {
        let filter_expr = "Elo >= 2400";
        let parsed = parse_filter_expression(filter_expr).unwrap();
        assert_eq!(
            parsed,
            vec![
                FilterComponent::OpenParenthesis,
                FilterComponent::Expression(FilterExpression {
                    field: Field::WhiteElo,
                    operator: Operator::Ge,
                    value: "2400".to_string(),
                    regex: None,
                }),
                FilterComponent::LogicalOperator(LogicalOperator::Or),
                FilterComponent::Expression(FilterExpression {
                    field: Field::BlackElo,
                    operator: Operator::Ge,
                    value: "2400".to_string(),
                    regex: None,
                }),
                FilterComponent::CloseParenthesis
            ]
        );
    }

    #[test]
    fn test_parse_filter_expression_title() {
        let filter_expr = "Title = GM";
        let parsed = parse_filter_expression(filter_expr).unwrap();
        assert_eq!(
            parsed,
            vec![
                FilterComponent::OpenParenthesis,
                FilterComponent::Expression(FilterExpression {
                    field: Field::WhiteTitle,
                    operator: Operator::Eq,
                    value: "GM".to_string(),
                    regex: None,
                }),
                FilterComponent::LogicalOperator(LogicalOperator::Or),
                FilterComponent::Expression(FilterExpression {
                    field: Field::BlackTitle,
                    operator: Operator::Eq,
                    value: "GM".to_string(),
                    regex: None,
                }),
                FilterComponent::CloseParenthesis
            ]
        );
    }

    #[test]
    fn test_parse_filter_expression_ratingdiff() {
        let filter_expr = "RatingDiff < 10";
        let parsed = parse_filter_expression(filter_expr).unwrap();
        assert_eq!(
            parsed,
            vec![
                FilterComponent::OpenParenthesis,
                FilterComponent::Expression(FilterExpression {
                    field: Field::WhiteRatingDiff,
                    operator: Operator::Lt,
                    value: "10".to_string(),
                    regex: None,
                }),
                FilterComponent::LogicalOperator(LogicalOperator::Or),
                FilterComponent::Expression(FilterExpression {
                    field: Field::BlackRatingDiff,
                    operator: Operator::Lt,
                    value: "10".to_string(),
                    regex: None,
                }),
                FilterComponent::CloseParenthesis
            ]
        );
    }
}
