// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! This module contains implementations of operations (unary, binary etc.) for DataFusion expressions.

use crate::expr::{Exists, Expr, InList, InSubquery, Like};
use crate::expr_fn::binary_expr;
use datafusion_expr_common::operator::Operator;
use std::ops::{self, Not};

/// Support `<expr> + <expr>` fluent style
impl ops::Add for Expr {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        binary_expr(self, Operator::Plus, rhs)
    }
}

/// Support `<expr> - <expr>` fluent style
impl ops::Sub for Expr {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self {
        binary_expr(self, Operator::Minus, rhs)
    }
}

/// Support `<expr> * <expr>` fluent style
impl ops::Mul for Expr {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self {
        binary_expr(self, Operator::Multiply, rhs)
    }
}

/// Support `<expr> / <expr>` fluent style
impl ops::Div for Expr {
    type Output = Self;

    fn div(self, rhs: Self) -> Self {
        binary_expr(self, Operator::Divide, rhs)
    }
}

/// Support `<expr> % <expr>` fluent style
impl ops::Rem for Expr {
    type Output = Self;

    fn rem(self, rhs: Self) -> Self {
        binary_expr(self, Operator::Modulo, rhs)
    }
}

/// Support `<expr> & <expr>` fluent style
impl ops::BitAnd for Expr {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self {
        binary_expr(self, Operator::BitwiseAnd, rhs)
    }
}

/// Support `<expr> | <expr>` fluent style
impl ops::BitOr for Expr {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self {
        binary_expr(self, Operator::BitwiseOr, rhs)
    }
}

/// Support `<expr> ^ <expr>` fluent style
impl ops::BitXor for Expr {
    type Output = Self;

    fn bitxor(self, rhs: Self) -> Self {
        binary_expr(self, Operator::BitwiseXor, rhs)
    }
}

/// Support `<expr> << <expr>` fluent style
impl ops::Shl for Expr {
    type Output = Self;

    fn shl(self, rhs: Self) -> Self::Output {
        binary_expr(self, Operator::BitwiseShiftLeft, rhs)
    }
}

/// Support `<expr> >> <expr>` fluent style
impl ops::Shr for Expr {
    type Output = Self;

    fn shr(self, rhs: Self) -> Self::Output {
        binary_expr(self, Operator::BitwiseShiftRight, rhs)
    }
}

/// Support `- <expr>` fluent style
impl ops::Neg for Expr {
    type Output = Self;

    fn neg(self) -> Self::Output {
        Expr::Negative(Box::new(self))
    }
}

/// Support `NOT <expr>` fluent style
impl Not for Expr {
    type Output = Self;

    fn not(self) -> Self::Output {
        match self {
            Expr::Like(Like {
                negated,
                expr,
                pattern,
                escape_char,
                case_insensitive,
            }) => Expr::Like(Like::new(
                !negated,
                expr,
                pattern,
                escape_char,
                case_insensitive,
            )),
            Expr::SimilarTo(Like {
                negated,
                expr,
                pattern,
                escape_char,
                case_insensitive,
            }) => Expr::SimilarTo(Like::new(
                !negated,
                expr,
                pattern,
                escape_char,
                case_insensitive,
            )),
            Expr::InList(InList {
                expr,
                list,
                negated,
            }) => Expr::InList(InList::new(expr, list, !negated)),
            Expr::Exists(Exists { subquery, negated }) => {
                Expr::Exists(Exists::new(subquery, !negated))
            }
            Expr::InSubquery(InSubquery {
                expr,
                subquery,
                negated,
            }) => Expr::InSubquery(InSubquery::new(expr, subquery, !negated)),
            _ => Expr::Not(Box::new(self)),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::lit;

    #[test]
    fn test_operators() {
        // Add
        assert_eq!(
            format!("{}", lit(1u32) + lit(2u32)),
            "UInt32(1) + UInt32(2)"
        );
        // Sub
        assert_eq!(
            format!("{}", lit(1u32) - lit(2u32)),
            "UInt32(1) - UInt32(2)"
        );
        // Mul
        assert_eq!(
            format!("{}", lit(1u32) * lit(2u32)),
            "UInt32(1) * UInt32(2)"
        );
        // Div
        assert_eq!(
            format!("{}", lit(1u32) / lit(2u32)),
            "UInt32(1) / UInt32(2)"
        );
        // Rem
        assert_eq!(
            format!("{}", lit(1u32) % lit(2u32)),
            "UInt32(1) % UInt32(2)"
        );
        // BitAnd
        assert_eq!(
            format!("{}", lit(1u32) & lit(2u32)),
            "UInt32(1) & UInt32(2)"
        );
        // BitOr
        assert_eq!(
            format!("{}", lit(1u32) | lit(2u32)),
            "UInt32(1) | UInt32(2)"
        );
        // BitXor
        assert_eq!(
            format!("{}", lit(1u32) ^ lit(2u32)),
            "UInt32(1) BIT_XOR UInt32(2)"
        );
        // Shl
        assert_eq!(
            format!("{}", lit(1u32) << lit(2u32)),
            "UInt32(1) << UInt32(2)"
        );
        // Shr
        assert_eq!(
            format!("{}", lit(1u32) >> lit(2u32)),
            "UInt32(1) >> UInt32(2)"
        );
        // Neg
        assert_eq!(format!("{}", -lit(1u32)), "(- UInt32(1))");
        // Not
        assert_eq!(format!("{}", !lit(1u32)), "NOT UInt32(1)");
    }
}
