#![warn(clippy::manual_clamp)]
#![allow(
    unused,
    dead_code,
    clippy::unnecessary_operation,
    clippy::no_effect,
    clippy::if_same_then_else
)]

use std::cmp::{max as cmp_max, min as cmp_min};

const CONST_MAX: i32 = 10;
const CONST_MIN: i32 = 4;

const CONST_F64_MAX: f64 = 10.0;
const CONST_F64_MIN: f64 = 4.0;

fn main() {
    let (input, min, max) = (0, -2, 3);
    // Min and max are not const, so this shouldn't trigger the lint.
    let x0 = if max < input {
        max
    } else if min > input {
        min
    } else {
        input
    };

    let x1 = if input > max {
        max
    } else if input < min {
        min
    } else {
        input
    };

    let x2 = if input < min {
        min
    } else if input > max {
        max
    } else {
        input
    };

    let x3 = if min > input {
        min
    } else if max < input {
        max
    } else {
        input
    };

    let x4 = input.max(min).min(max);

    let x5 = input.min(max).max(min);

    let x6 = match input {
        x if x > max => max,
        x if x < min => min,
        x => x,
    };

    let x7 = match input {
        x if x < min => min,
        x if x > max => max,
        x => x,
    };

    let x8 = match input {
        x if max < x => max,
        x if min > x => min,
        x => x,
    };

    let mut x9 = input;
    if x9 < min {
        x9 = min;
    }
    if x9 > max {
        x9 = max;
    }

    let x10 = match input {
        x if min > x => min,
        x if max < x => max,
        x => x,
    };

    let mut x11 = input;
    let _ = 1;
    if x11 > max {
        x11 = max;
    }
    if x11 < min {
        x11 = min;
    }

    let mut x12 = input;
    if min > x12 {
        x12 = min;
    }
    if max < x12 {
        x12 = max;
    }

    let mut x13 = input;
    if max < x13 {
        x13 = max;
    }
    if min > x13 {
        x13 = min;
    }

    {
        let (input, min, max) = (0.0f64, -2.0, 3.0);
        let x14 = if input > max {
            max
        } else if input < min {
            min
        } else {
            input
        };
    }
    let mut x15 = input;
    if x15 < min {
        x15 = min;
    } else if x15 > max {
        x15 = max;
    }

    // It's important this be the last set of statements
    let mut x16 = input;
    if max < x16 {
        x16 = max;
    }
    if min > x16 {
        x16 = min;
    }
}

fn const_main() {
    let input = 0;
    // Min and max are const, so this should trigger the lint.
    let x0 = if CONST_MAX < input {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        CONST_MAX
    } else if CONST_MIN > input {
        CONST_MIN
    } else {
        input
    };

    let x1 = if input > CONST_MAX {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        CONST_MAX
    } else if input < CONST_MIN {
        CONST_MIN
    } else {
        input
    };

    let x2 = if input < CONST_MIN {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        CONST_MIN
    } else if input > CONST_MAX {
        CONST_MAX
    } else {
        input
    };

    let x3 = if CONST_MIN > input {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        CONST_MIN
    } else if CONST_MAX < input {
        CONST_MAX
    } else {
        input
    };

    let x4 = input.max(CONST_MIN).min(CONST_MAX);
    //~^ ERROR: clamp-like pattern without using clamp function
    //~| NOTE: clamp will panic if max < min

    let x5 = input.min(CONST_MAX).max(CONST_MIN);
    //~^ ERROR: clamp-like pattern without using clamp function
    //~| NOTE: clamp will panic if max < min

    let x6 = match input {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x if x > CONST_MAX => CONST_MAX,
        x if x < CONST_MIN => CONST_MIN,
        x => x,
    };

    let x7 = match input {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x if x < CONST_MIN => CONST_MIN,
        x if x > CONST_MAX => CONST_MAX,
        x => x,
    };

    let x8 = match input {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x if CONST_MAX < x => CONST_MAX,
        x if CONST_MIN > x => CONST_MIN,
        x => x,
    };

    let mut x9 = input;
    if x9 < CONST_MIN {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x9 = CONST_MIN;
    }
    if x9 > CONST_MAX {
        x9 = CONST_MAX;
    }

    let x10 = match input {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x if CONST_MIN > x => CONST_MIN,
        x if CONST_MAX < x => CONST_MAX,
        x => x,
    };

    let mut x11 = input;
    let _ = 1;
    if x11 > CONST_MAX {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x11 = CONST_MAX;
    }
    if x11 < CONST_MIN {
        x11 = CONST_MIN;
    }

    let mut x12 = input;
    if CONST_MIN > x12 {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x12 = CONST_MIN;
    }
    if CONST_MAX < x12 {
        x12 = CONST_MAX;
    }

    let mut x13 = input;
    if CONST_MAX < x13 {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x13 = CONST_MAX;
    }
    if CONST_MIN > x13 {
        x13 = CONST_MIN;
    }

    let x14 = if input > CONST_MAX {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        CONST_MAX
    } else if input < CONST_MIN {
        CONST_MIN
    } else {
        input
    };
    {
        let input = 0.0f64;
        let x15 = if input > CONST_F64_MAX {
            //~^ ERROR: clamp-like pattern without using clamp function
            //~| NOTE: clamp will panic if max < min
            CONST_F64_MAX
        } else if input < CONST_F64_MIN {
            CONST_F64_MIN
        } else {
            input
        };
    }
    {
        let input: i32 = cmp_min_max(1);
        // These can only be detected if exactly one of the arguments to the inner function is const.
        let x16 = cmp_max(cmp_min(input, CONST_MAX), CONST_MIN);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x17 = cmp_min(cmp_max(input, CONST_MIN), CONST_MAX);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x18 = cmp_max(CONST_MIN, cmp_min(input, CONST_MAX));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x19 = cmp_min(CONST_MAX, cmp_max(input, CONST_MIN));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x20 = cmp_max(cmp_min(CONST_MAX, input), CONST_MIN);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x21 = cmp_min(cmp_max(CONST_MIN, input), CONST_MAX);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x22 = cmp_max(CONST_MIN, cmp_min(CONST_MAX, input));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let x23 = cmp_min(CONST_MAX, cmp_max(CONST_MIN, input));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        let input: f64 = cmp_min_max(1) as f64;
        let x24 = f64::max(f64::min(input, CONST_F64_MAX), CONST_F64_MIN);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x25 = f64::min(f64::max(input, CONST_F64_MIN), CONST_F64_MAX);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x26 = f64::max(CONST_F64_MIN, f64::min(input, CONST_F64_MAX));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x27 = f64::min(CONST_F64_MAX, f64::max(input, CONST_F64_MIN));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x28 = f64::max(f64::min(CONST_F64_MAX, input), CONST_F64_MIN);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x29 = f64::min(f64::max(CONST_F64_MIN, input), CONST_F64_MAX);
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x30 = f64::max(CONST_F64_MIN, f64::min(CONST_F64_MAX, input));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
        let x31 = f64::min(CONST_F64_MAX, f64::max(CONST_F64_MIN, input));
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min, min.is_nan(), or max.is_nan()
    }
    let mut x32 = input;
    if x32 < CONST_MIN {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x32 = CONST_MIN;
    } else if x32 > CONST_MAX {
        x32 = CONST_MAX;
    }

    // Flip the script, swap the places of min and max. Make sure this doesn't
    // trigger when clamp would be guaranteed to panic.
    let mut x33 = input;
    if x33 < CONST_MAX {
        x33 = CONST_MAX;
    } else if x33 > CONST_MIN {
        x33 = CONST_MIN;
    }

    // Do it again for NaN
    #[allow(invalid_nan_comparisons)]
    {
        let mut x34 = input as f64;
        if x34 < f64::NAN {
            x34 = f64::NAN;
        } else if x34 > CONST_F64_MAX {
            x34 = CONST_F64_MAX;
        }
    }

    // It's important this be the last set of statements
    let mut x35 = input;
    if CONST_MAX < x35 {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        x35 = CONST_MAX;
    }
    if CONST_MIN > x35 {
        x35 = CONST_MIN;
    }
}

// This code intentionally nonsense.
fn no_lint() {
    let input = 0;
    let x0 = if CONST_MAX < input {
        CONST_MAX
    } else if CONST_MIN > input {
        CONST_MAX
    } else {
        CONST_MIN
    };

    let x1 = if input > CONST_MAX {
        CONST_MAX
    } else if input > CONST_MIN {
        CONST_MIN
    } else {
        CONST_MAX
    };

    let x2 = if CONST_MAX < CONST_MIN {
        CONST_MIN
    } else if input > CONST_MAX {
        input
    } else {
        input
    };

    let x3 = if CONST_MIN > input {
        input
    } else if CONST_MAX < input {
        CONST_MAX
    } else {
        CONST_MAX
    };

    let x6 = match input {
        x if x < CONST_MAX => x,
        x if x < CONST_MIN => x,
        x => x,
    };

    let x7 = match input {
        x if x < CONST_MIN => CONST_MAX,
        x if x > CONST_MAX => CONST_MIN,
        x => x,
    };

    let x8 = match input {
        x if CONST_MAX > x => CONST_MAX,
        x if CONST_MIN > x => CONST_MIN,
        x => x,
    };

    let mut x9 = input;
    if x9 > CONST_MIN {
        x9 = CONST_MIN;
    }
    if x9 > CONST_MAX {
        x9 = CONST_MAX;
    }

    let x10 = match input {
        x if CONST_MIN > x => CONST_MIN,
        x if CONST_MAX < x => CONST_MAX,
        x => CONST_MIN,
    };

    let mut x11 = input;
    if x11 > CONST_MAX {
        x11 = CONST_MIN;
    }
    if x11 < CONST_MIN {
        x11 = CONST_MAX;
    }

    let mut x12 = input;
    if CONST_MIN > x12 {
        x12 = CONST_MAX * 3;
    }
    if CONST_MAX < x12 {
        x12 = CONST_MIN;
    }

    let mut x13 = input;
    if CONST_MAX < x13 {
        let x13 = CONST_MAX;
    }
    if CONST_MIN > x13 {
        x13 = CONST_MIN;
    }
    let mut x14 = input;
    if x14 < CONST_MIN {
        x14 = 3;
    } else if x14 > CONST_MAX {
        x14 = CONST_MAX;
    }
    {
        let input: i32 = cmp_min_max(1);
        // These can only be detected if exactly one of the arguments to the inner function is const.
        let x16 = cmp_max(cmp_max(input, CONST_MAX), CONST_MIN);
        let x17 = cmp_min(cmp_min(input, CONST_MIN), CONST_MAX);
        let x18 = cmp_max(CONST_MIN, cmp_max(input, CONST_MAX));
        let x19 = cmp_min(CONST_MAX, cmp_min(input, CONST_MIN));
        let x20 = cmp_max(cmp_max(CONST_MAX, input), CONST_MIN);
        let x21 = cmp_min(cmp_min(CONST_MIN, input), CONST_MAX);
        let x22 = cmp_max(CONST_MIN, cmp_max(CONST_MAX, input));
        let x23 = cmp_min(CONST_MAX, cmp_min(CONST_MIN, input));
        let input: f64 = cmp_min_max(1) as f64;
        let x24 = f64::max(f64::max(input, CONST_F64_MAX), CONST_F64_MIN);
        let x25 = f64::min(f64::min(input, CONST_F64_MIN), CONST_F64_MAX);
        let x26 = f64::max(CONST_F64_MIN, f64::max(input, CONST_F64_MAX));
        let x27 = f64::min(CONST_F64_MAX, f64::min(input, CONST_F64_MIN));
        let x28 = f64::max(f64::max(CONST_F64_MAX, input), CONST_F64_MIN);
        let x29 = f64::min(f64::min(CONST_F64_MIN, input), CONST_F64_MAX);
        let x30 = f64::max(CONST_F64_MIN, f64::max(CONST_F64_MAX, input));
        let x31 = f64::min(CONST_F64_MAX, f64::min(CONST_F64_MIN, input));
        let x32 = f64::min(CONST_F64_MAX, f64::min(CONST_F64_MIN, CONST_F64_MAX));
    }
}

fn dont_tell_me_what_to_do() {
    let (input, min, max) = (0, -2, 3);
    let mut x_never = input;
    #[allow(clippy::manual_clamp)]
    if x_never < min {
        x_never = min;
    }
    if x_never > max {
        x_never = max;
    }
}

/// Just to ensure this isn't const evaled
fn cmp_min_max(input: i32) -> i32 {
    input * 3
}

#[clippy::msrv = "1.49"]
fn msrv_1_49() {
    let (input, min, max) = (0, -1, 2);
    let _ = if input < min {
        min
    } else if input > max {
        max
    } else {
        input
    };
}

#[clippy::msrv = "1.50"]
fn msrv_1_50() {
    let input = 0;
    let _ = if input > CONST_MAX {
        //~^ ERROR: clamp-like pattern without using clamp function
        //~| NOTE: clamp will panic if max < min
        CONST_MAX
    } else if input < CONST_MIN {
        CONST_MIN
    } else {
        input
    };
}

const fn _const() {
    let (input, min, max) = (0, -1, 2);
    let _ = if input < min {
        min
    } else if input > max {
        max
    } else {
        input
    };

    let mut x = input;
    if max < x {
        let x = max;
    }
    if min > x {
        x = min;
    }
}
