use std::{
    borrow::Cow,
    fmt::{Display, Formatter},
};

use crate::antlr::hamelinparser;
use antlr_rust::token::{GenericToken, Token};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BinaryOp {
    // Arithmetic
    Add,
    Subtract,
    Multiply,
    Divide,
    Modulo,

    // Comparison
    Equal,
    NotEqual,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual,

    // Logical
    And, // AND
    Or,  // OR

    // Other operators from grammar
    Is,
    IsNot,
    In,
    NotIn,
    Range,
    Dot,
    Colon,
    As,
}

impl Display for BinaryOp {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let op_str = match self {
            BinaryOp::Add => "+",
            BinaryOp::Subtract => "-",
            BinaryOp::Multiply => "*",
            BinaryOp::Divide => "/",
            BinaryOp::Modulo => "%",
            BinaryOp::Equal => "==",
            BinaryOp::NotEqual => "!=",
            BinaryOp::LessThan => "<",
            BinaryOp::LessThanOrEqual => "<=",
            BinaryOp::GreaterThan => ">",
            BinaryOp::GreaterThanOrEqual => ">=",
            BinaryOp::And => "AND",
            BinaryOp::Or => "OR",
            BinaryOp::Is => "IS",
            BinaryOp::IsNot => "IS NOT",
            BinaryOp::In => "IN",
            BinaryOp::NotIn => "NOT IN",
            BinaryOp::Range => "..",
            BinaryOp::Dot => ".",
            BinaryOp::Colon => ":",
            BinaryOp::As => "AS",
        };
        write!(f, "{}", op_str)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UnaryPrefixOp {
    Plus,
    Minus,
    Not,
    Range,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UnaryPostfixOp {
    Range,
}

impl Display for UnaryPrefixOp {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let op_str = match self {
            UnaryPrefixOp::Plus => "+",
            UnaryPrefixOp::Minus => "-",
            UnaryPrefixOp::Not => "NOT",
            UnaryPrefixOp::Range => "..",
        };
        write!(f, "{}", op_str)
    }
}

impl Display for UnaryPostfixOp {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let op_str = match self {
            UnaryPostfixOp::Range => "..",
        };
        write!(f, "{}", op_str)
    }
}

impl BinaryOp {
    /// Convert from CST token to BinaryOp
    pub fn from_cst(token: &GenericToken<Cow<str>>) -> Option<Self> {
        match token.get_token_type() {
            hamelinparser::PLUS => Some(BinaryOp::Add),
            hamelinparser::MINUS => Some(BinaryOp::Subtract),
            hamelinparser::ASTERISK => Some(BinaryOp::Multiply),
            hamelinparser::SLASH => Some(BinaryOp::Divide),
            hamelinparser::PERCENT => Some(BinaryOp::Modulo),
            hamelinparser::EQ => Some(BinaryOp::Equal),
            hamelinparser::NEQ => Some(BinaryOp::NotEqual),
            hamelinparser::LT => Some(BinaryOp::LessThan),
            hamelinparser::LTE => Some(BinaryOp::LessThanOrEqual),
            hamelinparser::GT => Some(BinaryOp::GreaterThan),
            hamelinparser::GTE => Some(BinaryOp::GreaterThanOrEqual),
            hamelinparser::AND => Some(BinaryOp::And),
            hamelinparser::OR => Some(BinaryOp::Or),
            hamelinparser::IS => Some(BinaryOp::Is),
            hamelinparser::ISNOT => Some(BinaryOp::IsNot),
            hamelinparser::IN => Some(BinaryOp::In),
            hamelinparser::NOTIN => Some(BinaryOp::NotIn),
            hamelinparser::RANGE => Some(BinaryOp::Range),
            hamelinparser::DOT => Some(BinaryOp::Dot),
            hamelinparser::COLON => Some(BinaryOp::Colon),
            hamelinparser::AS => Some(BinaryOp::As),
            _ => None,
        }
    }
}

impl UnaryPrefixOp {
    /// Convert from CST token to UnaryPrefixOp
    pub fn from_cst(token: &GenericToken<Cow<str>>) -> Option<Self> {
        match token.get_token_type() {
            hamelinparser::PLUS => Some(UnaryPrefixOp::Plus),
            hamelinparser::MINUS => Some(UnaryPrefixOp::Minus),
            hamelinparser::NOT => Some(UnaryPrefixOp::Not),
            hamelinparser::RANGE => Some(UnaryPrefixOp::Range),
            _ => None,
        }
    }
}

impl UnaryPostfixOp {
    /// Convert from CST token to UnaryPostfixOp
    pub fn from_cst(token: &GenericToken<Cow<str>>) -> Option<Self> {
        match token.get_token_type() {
            hamelinparser::RANGE => Some(UnaryPostfixOp::Range),
            _ => None,
        }
    }
}
