use {
    crate::ast::ToSql,
    serde::{Deserialize, Serialize},
};

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum UnaryOperator {
    Plus,
    Minus,
    Not,
    Factorial,
    BitwiseNot,
}

impl ToSql for UnaryOperator {
    fn to_sql(&self) -> String {
        match self {
            UnaryOperator::Plus => "+".to_owned(),
            UnaryOperator::Minus => "-".to_owned(),
            UnaryOperator::Not => "NOT ".to_owned(),
            UnaryOperator::Factorial => "!".to_owned(),
            UnaryOperator::BitwiseNot => "~".to_owned(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BinaryOperator {
    Plus,
    Minus,
    Multiply,
    Divide,
    Modulo,
    StringConcat,
    Gt,
    Lt,
    GtEq,
    LtEq,
    Eq,
    NotEq,
    And,
    Or,
    Xor,
    BitwiseAnd,
    BitwiseShiftLeft,
    BitwiseShiftRight,
    Arrow,
}

impl ToSql for BinaryOperator {
    fn to_sql(&self) -> String {
        match self {
            BinaryOperator::Minus => "-".to_owned(),
            BinaryOperator::Multiply => "*".to_owned(),
            BinaryOperator::Divide => "/".to_owned(),
            BinaryOperator::Modulo => "%".to_owned(),
            BinaryOperator::Plus | BinaryOperator::StringConcat => "+".to_owned(),
            BinaryOperator::Gt => ">".to_owned(),
            BinaryOperator::Lt => "<".to_owned(),
            BinaryOperator::GtEq => ">=".to_owned(),
            BinaryOperator::LtEq => "<=".to_owned(),
            BinaryOperator::Eq => "=".to_owned(),
            BinaryOperator::NotEq => "<>".to_owned(),
            BinaryOperator::And => "AND".to_owned(),
            BinaryOperator::Or => "OR".to_owned(),
            BinaryOperator::Xor => "XOR".to_owned(),
            BinaryOperator::BitwiseAnd => "&".to_owned(),
            BinaryOperator::BitwiseShiftLeft => "<<".to_owned(),
            BinaryOperator::BitwiseShiftRight => ">>".to_owned(),
            BinaryOperator::Arrow => "->".to_owned(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum IndexOperator {
    Gt,
    Lt,
    GtEq,
    LtEq,
    Eq,
}

impl IndexOperator {
    #[must_use]
    pub fn reverse(self) -> Self {
        use IndexOperator::*;

        match self {
            Gt => Lt,
            Lt => Gt,
            GtEq => LtEq,
            LtEq => GtEq,
            Eq => Eq,
        }
    }
}

impl From<IndexOperator> for BinaryOperator {
    fn from(index_op: IndexOperator) -> Self {
        match index_op {
            IndexOperator::Gt => BinaryOperator::Gt,
            IndexOperator::Lt => BinaryOperator::Lt,
            IndexOperator::GtEq => BinaryOperator::GtEq,
            IndexOperator::LtEq => BinaryOperator::LtEq,
            IndexOperator::Eq => BinaryOperator::Eq,
        }
    }
}

#[cfg(test)]
mod tests {
    use {
        crate::ast::{BinaryOperator, Expr, Literal, ToSql, UnaryOperator},
        bigdecimal::BigDecimal,
    };
    #[test]
    fn to_sql() {
        assert_eq!(
            "1 + 2",
            Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1)))),
                op: BinaryOperator::Plus,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(2))))
            }
            .to_sql()
        );

        assert_eq!(
            "100 - 10",
            Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(100)))),
                op: BinaryOperator::Minus,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(10))))
            }
            .to_sql()
        );

        assert_eq!(
            "1024 * 1024",
            Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::Multiply,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024))))
            }
            .to_sql()
        );

        assert_eq!(
            "1024 / 8",
            Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::Divide,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(8))))
            }
            .to_sql()
        );

        assert_eq!(
            "1024 % 4",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::Modulo,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(4))))
            }
            .to_sql()
        );

        assert_eq!(
            "'Glue' + 'SQL'",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::QuotedString("Glue".to_owned()))),
                op: BinaryOperator::StringConcat,
                right: Box::new(Expr::Literal(Literal::QuotedString("SQL".to_owned())))
            }
            .to_sql()
        );
        assert_eq!(
            "1024 > 4",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::Gt,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(4))))
            }
            .to_sql()
        );
        assert_eq!(
            "8 < 1024",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(8)))),
                op: BinaryOperator::Lt,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024))))
            }
            .to_sql()
        );
        assert_eq!(
            "1024 >= 1024",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::GtEq,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024))))
            }
            .to_sql()
        );
        assert_eq!(
            "8 <= 8",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(8)))),
                op: BinaryOperator::LtEq,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(8))))
            }
            .to_sql()
        );
        assert_eq!(
            "1024 = 1024",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::Eq,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024))))
            }
            .to_sql()
        );
        assert_eq!(
            "1024 <> 1024",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024)))),
                op: BinaryOperator::NotEq,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1024))))
            }
            .to_sql()
        );
        assert_eq!(
            "1 << 2",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1)))),
                op: BinaryOperator::BitwiseShiftLeft,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(2))))
            }
            .to_sql()
        );
        assert_eq!(
            "1 >> 2",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1)))),
                op: BinaryOperator::BitwiseShiftRight,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(2))))
            }
            .to_sql()
        );
        assert_eq!(
            r#""condition_0" AND "condition_1""#,
            &Expr::BinaryOp {
                left: Box::new(Expr::Identifier("condition_0".to_owned())),
                op: BinaryOperator::And,
                right: Box::new(Expr::Identifier("condition_1".to_owned()))
            }
            .to_sql()
        );
        assert_eq!(
            r#""condition_0" OR "condition_1""#,
            &Expr::BinaryOp {
                left: Box::new(Expr::Identifier("condition_0".to_owned())),
                op: BinaryOperator::Or,
                right: Box::new(Expr::Identifier("condition_1".to_owned()))
            }
            .to_sql()
        );
        assert_eq!(
            r#""condition_0" XOR "condition_1""#,
            &Expr::BinaryOp {
                left: Box::new(Expr::Identifier("condition_0".to_owned())),
                op: BinaryOperator::Xor,
                right: Box::new(Expr::Identifier("condition_1".to_owned()))
            }
            .to_sql()
        );
        assert_eq!(
            "+8",
            Expr::UnaryOp {
                op: UnaryOperator::Plus,
                expr: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(8)))),
            }
            .to_sql(),
        );

        assert_eq!(
            "-8",
            Expr::UnaryOp {
                op: UnaryOperator::Minus,
                expr: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(8)))),
            }
            .to_sql(),
        );

        assert_eq!(
            r#"NOT "id""#,
            Expr::UnaryOp {
                op: UnaryOperator::Not,
                expr: Box::new(Expr::Identifier("id".to_owned())),
            }
            .to_sql(),
        );

        assert_eq!(
            "5!",
            Expr::UnaryOp {
                op: UnaryOperator::Factorial,
                expr: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(5)))),
            }
            .to_sql(),
        );

        assert_eq!(
            "29 & 15",
            &Expr::BinaryOp {
                left: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(29)))),
                op: BinaryOperator::BitwiseAnd,
                right: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(15))))
            }
            .to_sql()
        );

        assert_eq!(
            "~1",
            Expr::UnaryOp {
                op: UnaryOperator::BitwiseNot,
                expr: Box::new(Expr::Literal(Literal::Number(BigDecimal::from(1)))),
            }
            .to_sql(),
        );
    }
}
