#![allow(
    clippy::assign_op_pattern,
    clippy::erasing_op,
    clippy::identity_op,
    clippy::no_effect,
    clippy::op_ref,
    clippy::unnecessary_owned_empty_strings,
    arithmetic_overflow,
    unconditional_panic
)]
#![feature(const_mut_refs, inline_const, saturating_int_impl)]
#![warn(clippy::arithmetic_side_effects)]

use core::num::{Saturating, Wrapping};

const ONE: i32 = 1;
const ZERO: i32 = 0;

#[derive(Clone, Copy)]
pub struct Custom;

macro_rules! impl_arith {
    ( $( $_trait:ident, $lhs:ty, $rhs:ty, $method:ident; )* ) => {
        $(
            impl core::ops::$_trait<$lhs> for $rhs {
                type Output = Custom;
                fn $method(self, _: $lhs) -> Self::Output { todo!() }
            }
        )*
    }
}

macro_rules! impl_assign_arith {
    ( $( $_trait:ident, $lhs:ty, $rhs:ty, $method:ident; )* ) => {
        $(
            impl core::ops::$_trait<$lhs> for $rhs {
                fn $method(&mut self, _: $lhs) {}
            }
        )*
    }
}

impl_arith!(
    Add, Custom, Custom, add;
    Div, Custom, Custom, div;
    Mul, Custom, Custom, mul;
    Rem, Custom, Custom, rem;
    Shl, Custom, Custom, shl;
    Shr, Custom, Custom, shr;
    Sub, Custom, Custom, sub;

    Add, Custom, &Custom, add;
    Div, Custom, &Custom, div;
    Mul, Custom, &Custom, mul;
    Rem, Custom, &Custom, rem;
    Shl, Custom, &Custom, shl;
    Shr, Custom, &Custom, shr;
    Sub, Custom, &Custom, sub;

    Add, &Custom, Custom, add;
    Div, &Custom, Custom, div;
    Mul, &Custom, Custom, mul;
    Rem, &Custom, Custom, rem;
    Shl, &Custom, Custom, shl;
    Shr, &Custom, Custom, shr;
    Sub, &Custom, Custom, sub;

    Add, &Custom, &Custom, add;
    Div, &Custom, &Custom, div;
    Mul, &Custom, &Custom, mul;
    Rem, &Custom, &Custom, rem;
    Shl, &Custom, &Custom, shl;
    Shr, &Custom, &Custom, shr;
    Sub, &Custom, &Custom, sub;
);

impl_assign_arith!(
    AddAssign, Custom, Custom, add_assign;
    DivAssign, Custom, Custom, div_assign;
    MulAssign, Custom, Custom, mul_assign;
    RemAssign, Custom, Custom, rem_assign;
    ShlAssign, Custom, Custom, shl_assign;
    ShrAssign, Custom, Custom, shr_assign;
    SubAssign, Custom, Custom, sub_assign;

    AddAssign, Custom, &Custom, add_assign;
    DivAssign, Custom, &Custom, div_assign;
    MulAssign, Custom, &Custom, mul_assign;
    RemAssign, Custom, &Custom, rem_assign;
    ShlAssign, Custom, &Custom, shl_assign;
    ShrAssign, Custom, &Custom, shr_assign;
    SubAssign, Custom, &Custom, sub_assign;

    AddAssign, &Custom, Custom, add_assign;
    DivAssign, &Custom, Custom, div_assign;
    MulAssign, &Custom, Custom, mul_assign;
    RemAssign, &Custom, Custom, rem_assign;
    ShlAssign, &Custom, Custom, shl_assign;
    ShrAssign, &Custom, Custom, shr_assign;
    SubAssign, &Custom, Custom, sub_assign;

    AddAssign, &Custom, &Custom, add_assign;
    DivAssign, &Custom, &Custom, div_assign;
    MulAssign, &Custom, &Custom, mul_assign;
    RemAssign, &Custom, &Custom, rem_assign;
    ShlAssign, &Custom, &Custom, shl_assign;
    ShrAssign, &Custom, &Custom, shr_assign;
    SubAssign, &Custom, &Custom, sub_assign;
);

impl core::ops::Neg for Custom {
    type Output = Custom;
    fn neg(self) -> Self::Output {
        todo!()
    }
}
impl core::ops::Neg for &Custom {
    type Output = Custom;
    fn neg(self) -> Self::Output {
        todo!()
    }
}

pub fn association_with_structures_should_not_trigger_the_lint() {
    enum Foo {
        Bar = -2,
    }

    impl Trait for Foo {
        const ASSOC: i32 = {
            let _: [i32; 1 + 1];
            fn foo() {}
            1 + 1
        };
    }

    struct Baz([i32; 1 + 1]);

    trait Trait {
        const ASSOC: i32 = 1 + 1;
    }

    type Alias = [i32; 1 + 1];

    union Qux {
        field: [i32; 1 + 1],
    }

    let _: [i32; 1 + 1] = [0, 0];

    let _: [i32; 1 + 1] = {
        let a: [i32; 1 + 1] = [0, 0];
        a
    };
}

pub fn hard_coded_allowed() {
    let _ = 1f32 + 1f32;
    let _ = 1f64 + 1f64;

    let _ = Saturating(0u32) + Saturating(0u32);
    let _ = String::new() + "";
    let _ = Wrapping(0u32) + Wrapping(0u32);

    let saturating: Saturating<u32> = Saturating(0u32);
    let string: String = String::new();
    let wrapping: Wrapping<u32> = Wrapping(0u32);

    let inferred_saturating = saturating + saturating;
    let inferred_string = string + "";
    let inferred_wrapping = wrapping + wrapping;

    let _ = inferred_saturating + inferred_saturating;
    let _ = inferred_string + "";
    let _ = inferred_wrapping + inferred_wrapping;
}

#[rustfmt::skip]
pub fn const_ops_should_not_trigger_the_lint() {
    const _: i32 = { let mut n = 1; n += 1; n };
    let _ = const { let mut n = 1; n += 1; n };

    const _: i32 = { let mut n = 1; n = n + 1; n };
    let _ = const { let mut n = 1; n = n + 1; n };

    const _: i32 = { let mut n = 1; n = 1 + n; n };
    let _ = const { let mut n = 1; n = 1 + n; n };

    const _: i32 = 1 + 1;
    let _ = const { 1 + 1 };

    const _: i32 = { let mut n = 1; n = -1; n = -(-1); n = -n; n };
    let _ = const { let mut n = 1; n = -1; n = -(-1); n = -n; n };
}

pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_trigger_the_lint() {
    let mut _n = i32::MAX;

    // Assign
    _n += 0;
    _n += &0;
    _n -= 0;
    _n -= &0;
    _n += ZERO;
    _n += &ZERO;
    _n -= ZERO;
    _n -= &ZERO;
    _n /= 99;
    _n /= &99;
    _n %= 99;
    _n %= &99;
    _n *= 0;
    _n *= &0;
    _n *= 1;
    _n *= &1;
    _n *= ZERO;
    _n *= &ZERO;
    _n *= ONE;
    _n *= &ONE;
    _n += -0;
    _n += &-0;
    _n -= -0;
    _n -= &-0;
    _n += -ZERO;
    _n += &-ZERO;
    _n -= -ZERO;
    _n -= &-ZERO;
    _n /= -99;
    _n /= &-99;
    _n %= -99;
    _n %= &-99;
    _n *= -0;
    _n *= &-0;
    _n *= -1;
    _n *= &-1;

    // Binary
    _n = _n + 0;
    _n = _n + &0;
    _n = 0 + _n;
    _n = &0 + _n;
    _n = _n + ZERO;
    _n = _n + &ZERO;
    _n = ZERO + _n;
    _n = &ZERO + _n;
    _n = _n - 0;
    _n = _n - &0;
    _n = 0 - _n;
    _n = &0 - _n;
    _n = _n - ZERO;
    _n = _n - &ZERO;
    _n = ZERO - _n;
    _n = &ZERO - _n;
    _n = _n / 99;
    _n = _n / &99;
    _n = _n % 99;
    _n = _n % &99;
    _n = _n * 0;
    _n = _n * &0;
    _n = 0 * _n;
    _n = &0 * _n;
    _n = _n * 1;
    _n = _n * &1;
    _n = ZERO * _n;
    _n = &ZERO * _n;
    _n = _n * ONE;
    _n = _n * &ONE;
    _n = 1 * _n;
    _n = &1 * _n;
    _n = 23 + 85;

    // Unary
    _n = -2147483647;
    _n = -i32::MAX;
    _n = -i32::MIN;
    _n = -&2147483647;
    _n = -&i32::MAX;
    _n = -&i32::MIN;
}

pub fn unknown_ops_or_runtime_ops_that_can_overflow() {
    let mut _n = i32::MAX;
    let mut _custom = Custom;

    // Assign
    _n += 1;
    _n += &1;
    _n -= 1;
    _n -= &1;
    _n /= 0;
    _n /= &0;
    _n %= 0;
    _n %= &0;
    _n *= 2;
    _n *= &2;
    _n += -1;
    _n += &-1;
    _n -= -1;
    _n -= &-1;
    _n /= -0;
    _n /= &-0;
    _n %= -0;
    _n %= &-0;
    _n *= -2;
    _n *= &-2;
    _custom += Custom;
    _custom += &Custom;
    _custom -= Custom;
    _custom -= &Custom;
    _custom /= Custom;
    _custom /= &Custom;
    _custom %= Custom;
    _custom %= &Custom;
    _custom *= Custom;
    _custom *= &Custom;
    _custom >>= Custom;
    _custom >>= &Custom;
    _custom <<= Custom;
    _custom <<= &Custom;
    _custom += -Custom;
    _custom += &-Custom;
    _custom -= -Custom;
    _custom -= &-Custom;
    _custom /= -Custom;
    _custom /= &-Custom;
    _custom %= -Custom;
    _custom %= &-Custom;
    _custom *= -Custom;
    _custom *= &-Custom;
    _custom >>= -Custom;
    _custom >>= &-Custom;
    _custom <<= -Custom;
    _custom <<= &-Custom;

    // Binary
    _n = _n + 1;
    _n = _n + &1;
    _n = 1 + _n;
    _n = &1 + _n;
    _n = _n - 1;
    _n = _n - &1;
    _n = 1 - _n;
    _n = &1 - _n;
    _n = _n / 0;
    _n = _n / &0;
    _n = _n % 0;
    _n = _n % &0;
    _n = _n * 2;
    _n = _n * &2;
    _n = 2 * _n;
    _n = &2 * _n;
    _n = 23 + &85;
    _n = &23 + 85;
    _n = &23 + &85;
    _custom = _custom + _custom;
    _custom = _custom + &_custom;
    _custom = Custom + _custom;
    _custom = &Custom + _custom;
    _custom = _custom - Custom;
    _custom = _custom - &Custom;
    _custom = Custom - _custom;
    _custom = &Custom - _custom;
    _custom = _custom / Custom;
    _custom = _custom / &Custom;
    _custom = _custom % Custom;
    _custom = _custom % &Custom;
    _custom = _custom * Custom;
    _custom = _custom * &Custom;
    _custom = Custom * _custom;
    _custom = &Custom * _custom;
    _custom = Custom + &Custom;
    _custom = &Custom + Custom;
    _custom = &Custom + &Custom;
    _custom = _custom >> _custom;
    _custom = _custom >> &_custom;
    _custom = Custom << _custom;
    _custom = &Custom << _custom;

    // Unary
    _n = -_n;
    _n = -&_n;
    _custom = -_custom;
    _custom = -&_custom;
}

// Copied and pasted from the `integer_arithmetic` lint for comparison.
pub fn integer_arithmetic() {
    let mut i = 1i32;
    let mut var1 = 0i32;
    let mut var2 = -1i32;

    1 + i;
    i * 2;
    1 % i / 2;
    i - 2 + 2 - i;
    -i;
    i >> 1;
    i << 1;

    -1;
    -(-1);

    i & 1;
    i | 1;
    i ^ 1;

    i += 1;
    i -= 1;
    i *= 2;
    i /= 2;
    i /= 0;
    i /= -1;
    i /= var1;
    i /= var2;
    i %= 2;
    i %= 0;
    i %= -1;
    i %= var1;
    i %= var2;
    i <<= 3;
    i >>= 2;

    i |= 1;
    i &= 1;
    i ^= i;
}

pub fn issue_10583(a: u16) -> u16 {
    10 / a
}

fn main() {}
