use std::str::FromStr;

use bigdecimal::{BigDecimal, ToPrimitive};

use super::super::*;

pub(in super::super) fn stored_value_to_i64(value: &StoredValue) -> Option<i64> {
    match value {
        StoredValue::Int32(value) => Some(i64::from(*value)),
        StoredValue::Int64(value) => Some(*value),
        _ => None,
    }
}

pub(in super::super) fn eval_bitwise_binary_function(
    function_name: &str,
    args: Vec<StoredValue>,
    op_name: &str,
    op_fn: fn(i64, i64) -> i64,
) -> Result<StoredValue> {
    if args.len() != 2 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 2 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let left = iter.next().unwrap();
    let right = iter.next().unwrap();

    if left.is_null() || right.is_null() {
        return Ok(StoredValue::Null);
    }

    let both_i32 = matches!(
        (&left, &right),
        (StoredValue::Int32(_), StoredValue::Int32(_))
    );
    let left_value = stored_value_to_i64(&left).ok_or_else(|| {
        Error::Other(format!(
            "DB9 bitwise {} not supported for {}",
            op_name,
            left.kind_name()
        ))
    })?;
    let right_value = stored_value_to_i64(&right).ok_or_else(|| {
        Error::Other(format!(
            "DB9 bitwise {} not supported for {}",
            op_name,
            right.kind_name()
        ))
    })?;

    let result = op_fn(left_value, right_value);
    if both_i32 {
        Ok(StoredValue::Int32(result as i32))
    } else {
        Ok(StoredValue::Int64(result))
    }
}

pub(in super::super) fn eval_shift_function(
    function_name: &str,
    args: Vec<StoredValue>,
    op_name: &str,
    is_left: bool,
) -> Result<StoredValue> {
    if args.len() != 2 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 2 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let left = iter.next().unwrap();
    let right = iter.next().unwrap();

    if left.is_null() || right.is_null() {
        return Ok(StoredValue::Null);
    }

    let shift = match right {
        StoredValue::Int32(value) => value,
        other => {
            return Err(Error::Other(format!(
                "DB9 bitwise {} requires Int32 shift count, got {}",
                op_name,
                other.kind_name()
            )));
        }
    };

    match left {
        StoredValue::Int32(value) => {
            let shift = shift as u32;
            let result = if is_left {
                value.wrapping_shl(shift)
            } else {
                value.wrapping_shr(shift)
            };
            Ok(StoredValue::Int32(result))
        }
        StoredValue::Int64(value) => {
            let shift = shift as u32;
            let result = if is_left {
                value.wrapping_shl(shift)
            } else {
                value.wrapping_shr(shift)
            };
            Ok(StoredValue::Int64(result))
        }
        other => Err(Error::Other(format!(
            "DB9 bitwise {} not supported for {}",
            op_name,
            other.kind_name()
        ))),
    }
}

pub(in super::super) fn eval_bitwise_not_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 1 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 1 arg, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    match iter.next().unwrap() {
        StoredValue::Null => Ok(StoredValue::Null),
        StoredValue::Int32(value) => Ok(StoredValue::Int32(!value)),
        StoredValue::Int64(value) => Ok(StoredValue::Int64(!value)),
        other => Err(Error::Other(format!(
            "DB9 bitwise NOT not supported for {}",
            other.kind_name()
        ))),
    }
}

pub(in super::super) fn eval_trunc_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 1 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 1 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let value = iter.next().unwrap();

    match value {
        StoredValue::Float64(value) => Ok(StoredValue::Float64(value.trunc())),
        StoredValue::Int32(value) => Ok(StoredValue::Float64((value as f64).trunc())),
        StoredValue::Int64(value) => Ok(StoredValue::Float64((value as f64).trunc())),
        _ => Ok(StoredValue::Null),
    }
}

pub(in super::super) fn eval_float64_unary_math_function<F>(
    function_name: &str,
    args: Vec<StoredValue>,
    f: F,
) -> Result<StoredValue>
where
    F: FnOnce(f64) -> f64,
{
    eval_unary_function(function_name, args, |value| {
        match numeric_value_as_f64(value)? {
            Some(value) => Ok(StoredValue::Float64(f(value))),
            None => Ok(StoredValue::Null),
        }
    })
}

pub(in super::super) fn eval_float64_checked_unary_math_function<F>(
    function_name: &str,
    args: Vec<StoredValue>,
    f: F,
) -> Result<StoredValue>
where
    F: FnOnce(f64) -> Result<f64>,
{
    eval_unary_function(function_name, args, |value| {
        match numeric_value_as_f64(value)? {
            Some(value) => Ok(StoredValue::Float64(f(value)?)),
            None => Ok(StoredValue::Null),
        }
    })
}

fn invalid_argument_for_power_function(message: &str) -> Error {
    Error::Other(message.to_owned())
}

fn invalid_argument_for_logarithm(message: &str) -> Error {
    Error::Other(message.to_owned())
}

fn numeric_value_out_of_range(message: &str) -> Error {
    Error::Other(message.to_owned())
}

fn float_underflow_error() -> Error {
    numeric_value_out_of_range("value out of range: underflow")
}

fn trig_inverse_input_out_of_range(value: f64) -> bool {
    !value.is_nan() && !(-1.0..=1.0).contains(&value)
}

fn checked_float_exp(value: f64) -> Result<f64> {
    let result = value.exp();
    if value.is_finite() && result.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if value.is_finite() && result == 0.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

fn checked_float_power(base: f64, exponent: f64) -> Result<f64> {
    if base == 0.0 && exponent < 0.0 {
        return Err(invalid_argument_for_power_function(
            "zero raised to a negative power is undefined",
        ));
    }
    if !base.is_nan() && exponent.is_finite() && base < 0.0 && exponent.fract() != 0.0 {
        return Err(invalid_argument_for_power_function(
            "a negative number raised to a non-integer power yields a complex result",
        ));
    }

    let result = base.powf(exponent);
    if base.is_finite() && exponent.is_finite() && result.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if base.is_finite() && exponent.is_finite() && result == 0.0 && base != 0.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

fn checked_float_sqrt(value: f64) -> Result<f64> {
    if value < 0.0 {
        return Err(invalid_argument_for_power_function(
            "cannot take square root of a negative number",
        ));
    }
    let result = value.sqrt();
    if result.is_infinite() && !value.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if result == 0.0 && value != 0.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

fn checked_float_log(value: f64, f: impl FnOnce(f64) -> f64) -> Result<f64> {
    if value == 0.0 {
        return Err(invalid_argument_for_logarithm(
            "cannot take logarithm of zero",
        ));
    }
    if value < 0.0 {
        return Err(invalid_argument_for_logarithm(
            "cannot take logarithm of a negative number",
        ));
    }
    let result = f(value);
    if result.is_infinite() && !value.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if result == 0.0 && value != 1.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

pub(in super::super) fn checked_float_cbrt(value: f64) -> Result<f64> {
    let result = pg_like_cbrt(value);
    if result.is_infinite() && !value.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if result == 0.0 && value != 0.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

pub(in super::super) fn checked_float_degrees(value: f64) -> Result<f64> {
    let result = value.to_degrees();
    if result.is_infinite() && !value.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if result == 0.0 && value != 0.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

pub(in super::super) fn checked_float_radians(value: f64) -> Result<f64> {
    let result = value.to_radians();
    if result.is_infinite() && !value.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    if result == 0.0 && value != 0.0 {
        return Err(float_underflow_error());
    }
    Ok(result)
}

pub(in super::super) fn checked_float_trig(
    value: f64,
    f: impl FnOnce(f64) -> f64,
    check_inf_result: bool,
) -> Result<f64> {
    if value.is_nan() {
        return Ok(f64::NAN);
    }
    let result = f(value);
    if value.is_infinite() {
        return Err(numeric_value_out_of_range("input is out of range"));
    }
    if check_inf_result && result.is_infinite() {
        return Err(numeric_value_out_of_range("value overflows numeric format"));
    }
    Ok(result)
}

pub(in super::super) fn eval_sqrt_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| match value {
        StoredValue::Null => Ok(StoredValue::Null),
        StoredValue::Int32(value) => {
            if value < 0 {
                return Err(invalid_argument_for_power_function(
                    "cannot take square root of a negative number",
                ));
            }
            Ok(StoredValue::Float64((value as f64).sqrt()))
        }
        StoredValue::Int64(value) => {
            if value < 0 {
                return Err(invalid_argument_for_power_function(
                    "cannot take square root of a negative number",
                ));
            }
            Ok(StoredValue::Float64((value as f64).sqrt()))
        }
        StoredValue::Float64(value) => Ok(StoredValue::Float64(checked_float_sqrt(value)?)),
        StoredValue::Numeric(value) => {
            let value = stored_decimal_to_f64(&value)?;
            Ok(StoredValue::Float64(checked_float_sqrt(value)?))
        }
        _ => Ok(StoredValue::Null),
    })
}

pub(in super::super) fn eval_ln_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| match value {
        StoredValue::Null => Ok(StoredValue::Null),
        StoredValue::Int32(value) => {
            if value == 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of zero",
                ));
            }
            if value < 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of a negative number",
                ));
            }
            Ok(StoredValue::Float64((value as f64).ln()))
        }
        StoredValue::Int64(value) => {
            if value == 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of zero",
                ));
            }
            if value < 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of a negative number",
                ));
            }
            Ok(StoredValue::Float64((value as f64).ln()))
        }
        StoredValue::Float64(value) => Ok(StoredValue::Float64(checked_float_log(value, f64::ln)?)),
        StoredValue::Numeric(value) => {
            let value = stored_decimal_to_f64(&value)?;
            Ok(StoredValue::Float64(checked_float_log(value, f64::ln)?))
        }
        _ => Ok(StoredValue::Null),
    })
}

pub(in super::super) fn eval_log10_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| match value {
        StoredValue::Null => Ok(StoredValue::Null),
        StoredValue::Int32(value) => {
            if value == 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of zero",
                ));
            }
            if value < 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of a negative number",
                ));
            }
            Ok(StoredValue::Float64((value as f64).log10()))
        }
        StoredValue::Int64(value) => {
            if value == 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of zero",
                ));
            }
            if value < 0 {
                return Err(invalid_argument_for_logarithm(
                    "cannot take logarithm of a negative number",
                ));
            }
            Ok(StoredValue::Float64((value as f64).log10()))
        }
        StoredValue::Float64(value) => {
            Ok(StoredValue::Float64(checked_float_log(value, f64::log10)?))
        }
        StoredValue::Numeric(value) => {
            let value = stored_decimal_to_f64(&value)?;
            Ok(StoredValue::Float64(checked_float_log(value, f64::log10)?))
        }
        _ => Ok(StoredValue::Null),
    })
}

pub(in super::super) fn eval_exp_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| match value {
        StoredValue::Null => Ok(StoredValue::Null),
        StoredValue::Numeric(value) => {
            let value = stored_decimal_to_f64(&value)?;
            Ok(StoredValue::Float64(checked_float_exp(value)?))
        }
        other => match numeric_value_as_f64(other)? {
            Some(value) => Ok(StoredValue::Float64(checked_float_exp(value)?)),
            None => Ok(StoredValue::Null),
        },
    })
}

pub(in super::super) fn eval_asin_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| match value {
        StoredValue::Null => Ok(StoredValue::Null),
        other => match numeric_value_as_f64(other)? {
            Some(value) => {
                if trig_inverse_input_out_of_range(value) {
                    return Err(numeric_value_out_of_range("input is out of range"));
                }
                Ok(StoredValue::Float64(value.asin()))
            }
            None => Ok(StoredValue::Null),
        },
    })
}

pub(in super::super) fn eval_acos_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| match value {
        StoredValue::Null => Ok(StoredValue::Null),
        other => match numeric_value_as_f64(other)? {
            Some(value) => {
                if trig_inverse_input_out_of_range(value) {
                    return Err(numeric_value_out_of_range("input is out of range"));
                }
                Ok(StoredValue::Float64(value.acos()))
            }
            None => Ok(StoredValue::Null),
        },
    })
}

pub(in super::super) fn pg_like_cbrt(value: f64) -> f64 {
    // Keep this formula in sync with db9-server's local evaluator. PostgreSQL's
    // float8 CBRT result differs from Rust's standard-library `f64::cbrt`.
    if value == 0.0 {
        0.0
    } else {
        value.signum() * ((value.abs().ln() / 3.0).exp())
    }
}

pub(in super::super) fn eval_power_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 2 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 2 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let base = numeric_value_as_f64(iter.next().unwrap())?;
    let exponent = numeric_value_as_f64(iter.next().unwrap())?;
    match (base, exponent) {
        (Some(base), Some(exponent)) => {
            Ok(StoredValue::Float64(checked_float_power(base, exponent)?))
        }
        _ => Ok(StoredValue::Null),
    }
}

pub(in super::super) fn eval_mod_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 2 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 2 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let left = iter.next().unwrap();
    let right = iter.next().unwrap();

    match (left, right) {
        (StoredValue::Null, _) | (_, StoredValue::Null) => Ok(StoredValue::Null),
        (StoredValue::Int32(_), StoredValue::Int32(0))
        | (StoredValue::Int32(_), StoredValue::Int64(0))
        | (StoredValue::Int64(_), StoredValue::Int32(0))
        | (StoredValue::Int64(_), StoredValue::Int64(0)) => {
            Err(Error::Other("division by zero".to_owned()))
        }
        (StoredValue::Int32(_), StoredValue::Int32(-1)) => Ok(StoredValue::Int32(0)),
        (StoredValue::Int32(_), StoredValue::Int64(-1)) => Ok(StoredValue::Int64(0)),
        (StoredValue::Int64(_), StoredValue::Int32(-1)) => Ok(StoredValue::Int64(0)),
        (StoredValue::Int64(_), StoredValue::Int64(-1)) => Ok(StoredValue::Int64(0)),
        (StoredValue::Int32(left), StoredValue::Int32(right)) if right != 0 => left
            .checked_rem(right)
            .map(StoredValue::Int32)
            .ok_or_else(|| Error::Other("integer out of range".to_owned())),
        (StoredValue::Int32(left), StoredValue::Int64(right)) if right != 0 => i64::from(left)
            .checked_rem(right)
            .map(StoredValue::Int64)
            .ok_or_else(|| Error::Other("bigint out of range".to_owned())),
        (StoredValue::Int64(left), StoredValue::Int32(right)) if right != 0 => left
            .checked_rem(i64::from(right))
            .map(StoredValue::Int64)
            .ok_or_else(|| Error::Other("bigint out of range".to_owned())),
        (StoredValue::Int64(left), StoredValue::Int64(right)) if right != 0 => left
            .checked_rem(right)
            .map(StoredValue::Int64)
            .ok_or_else(|| Error::Other("bigint out of range".to_owned())),
        (left, right) => Err(Error::Other(format!(
            "DB9 function '{}' does not support {} and {}",
            function_name,
            left.kind_name(),
            right.kind_name()
        ))),
    }
}

pub(in super::super) fn eval_sign_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 1 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 1 arg, got {}",
            function_name,
            args.len()
        )));
    }

    match args.into_iter().next().unwrap() {
        StoredValue::Int32(value) => Ok(StoredValue::Float64(match value.cmp(&0) {
            std::cmp::Ordering::Greater => 1.0,
            std::cmp::Ordering::Less => -1.0,
            std::cmp::Ordering::Equal => 0.0,
        })),
        StoredValue::Int64(value) => Ok(StoredValue::Float64(match value.cmp(&0) {
            std::cmp::Ordering::Greater => 1.0,
            std::cmp::Ordering::Less => -1.0,
            std::cmp::Ordering::Equal => 0.0,
        })),
        StoredValue::Float64(value) => Ok(StoredValue::Float64(if value > 0.0 {
            1.0
        } else if value < 0.0 {
            -1.0
        } else {
            0.0
        })),
        StoredValue::Numeric(value) => {
            let sign = if stored_decimal_is_zero(&value) {
                "0"
            } else if value.negative {
                "-1"
            } else {
                "1"
            };
            Ok(StoredValue::Numeric(parse_stored_decimal(sign)?))
        }
        _ => Ok(StoredValue::Null),
    }
}

pub(in super::super) fn eval_atan2_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 2 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 2 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let y = numeric_value_as_f64(iter.next().unwrap())?;
    let x = numeric_value_as_f64(iter.next().unwrap())?;
    match (y, x) {
        (Some(y), Some(x)) => Ok(StoredValue::Float64(y.atan2(x))),
        _ => Ok(StoredValue::Null),
    }
}

pub(in super::super) fn eval_hashtext_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    eval_unary_function(function_name, args, |value| {
        let text = match value {
            StoredValue::Text(text) => text,
            StoredValue::Null => return Ok(StoredValue::Null),
            _ => return Ok(StoredValue::Null),
        };
        Ok(StoredValue::Int32(pg_hash_bytes(text.as_bytes()) as i32))
    })
}

pub(in super::super) fn pg_hash_mix(a: &mut u32, b: &mut u32, c: &mut u32) {
    *a = a.wrapping_sub(*c);
    *a ^= c.rotate_left(4);
    *c = c.wrapping_add(*b);
    *b = b.wrapping_sub(*a);
    *b ^= a.rotate_left(6);
    *a = a.wrapping_add(*c);
    *c = c.wrapping_sub(*b);
    *c ^= b.rotate_left(8);
    *b = b.wrapping_add(*a);
    *a = a.wrapping_sub(*c);
    *a ^= c.rotate_left(16);
    *c = c.wrapping_add(*b);
    *b = b.wrapping_sub(*a);
    *b ^= a.rotate_left(19);
    *a = a.wrapping_add(*c);
    *c = c.wrapping_sub(*b);
    *c ^= b.rotate_left(4);
    *b = b.wrapping_add(*a);
}

pub(in super::super) fn pg_hash_final(a: &mut u32, b: &mut u32, c: &mut u32) {
    *c ^= *b;
    *c = c.wrapping_sub(b.rotate_left(14));
    *a ^= *c;
    *a = a.wrapping_sub(c.rotate_left(11));
    *b ^= *a;
    *b = b.wrapping_sub(a.rotate_left(25));
    *c ^= *b;
    *c = c.wrapping_sub(b.rotate_left(16));
    *a ^= *c;
    *a = a.wrapping_sub(c.rotate_left(4));
    *b ^= *a;
    *b = b.wrapping_sub(a.rotate_left(14));
    *c ^= *b;
    *c = c.wrapping_sub(b.rotate_left(24));
}

pub(in super::super) fn pg_hash_bytes(bytes: &[u8]) -> u32 {
    let len = bytes.len() as u32;
    let mut a = 0x9e37_79b9_u32.wrapping_add(len).wrapping_add(3_923_095);
    let mut b = a;
    let mut c = a;
    let mut offset = 0usize;
    let mut remaining = bytes.len();

    while remaining >= 12 {
        a = a.wrapping_add(u32::from_le_bytes(
            bytes[offset..offset + 4].try_into().unwrap(),
        ));
        b = b.wrapping_add(u32::from_le_bytes(
            bytes[offset + 4..offset + 8].try_into().unwrap(),
        ));
        c = c.wrapping_add(u32::from_le_bytes(
            bytes[offset + 8..offset + 12].try_into().unwrap(),
        ));
        pg_hash_mix(&mut a, &mut b, &mut c);
        offset += 12;
        remaining -= 12;
    }

    let tail = &bytes[offset..];
    if remaining == 11 {
        c = c.wrapping_add((tail[10] as u32) << 24);
    }
    if remaining >= 10 {
        c = c.wrapping_add((tail[9] as u32) << 16);
    }
    if remaining >= 9 {
        c = c.wrapping_add((tail[8] as u32) << 8);
    }
    if remaining >= 8 {
        b = b.wrapping_add((tail[7] as u32) << 24);
    }
    if remaining >= 7 {
        b = b.wrapping_add((tail[6] as u32) << 16);
    }
    if remaining >= 6 {
        b = b.wrapping_add((tail[5] as u32) << 8);
    }
    if remaining >= 5 {
        b = b.wrapping_add(tail[4] as u32);
    }
    if remaining >= 4 {
        a = a.wrapping_add((tail[3] as u32) << 24);
    }
    if remaining >= 3 {
        a = a.wrapping_add((tail[2] as u32) << 16);
    }
    if remaining >= 2 {
        a = a.wrapping_add((tail[1] as u32) << 8);
    }
    if remaining >= 1 {
        a = a.wrapping_add(tail[0] as u32);
    }

    pg_hash_final(&mut a, &mut b, &mut c);
    c
}

pub(in super::super) fn eval_width_bucket_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 4 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 4 args, got {}",
            function_name,
            args.len()
        )));
    }

    let use_float_path = args[..3]
        .iter()
        .any(|arg| matches!(arg, StoredValue::Float64(_)));
    let use_numeric_path = args[..3]
        .iter()
        .any(|arg| matches!(arg, StoredValue::Numeric(_)));
    if use_numeric_path && !use_float_path {
        return eval_width_bucket_numeric_function(function_name, args);
    }

    let mut iter = args.into_iter();
    let operand = numeric_value_as_f64_strict(function_name, "operand", iter.next().unwrap())?;
    let low = numeric_value_as_f64_strict(function_name, "lower_bound", iter.next().unwrap())?;
    let high = numeric_value_as_f64_strict(function_name, "upper_bound", iter.next().unwrap())?;
    let count = integer_value_as_i32(function_name, "count", iter.next().unwrap())?;
    let (Some(operand), Some(low), Some(high), Some(count)) = (operand, low, high, count) else {
        return Ok(StoredValue::Null);
    };

    if operand.is_nan() || low.is_nan() || high.is_nan() {
        return Err(Error::Other(format!(
            "DB9 function '{}' operand, lower bound, and upper bound cannot be NaN",
            function_name
        )));
    }
    if !low.is_finite() || !high.is_finite() {
        return Err(Error::Other(format!(
            "DB9 function '{}' lower and upper bounds must be finite",
            function_name
        )));
    }
    if count <= 0 {
        return Err(Error::Other(format!(
            "DB9 function '{}' count must be greater than zero",
            function_name
        )));
    }
    if low == high {
        return Err(Error::Other(format!(
            "DB9 function '{}' lower bound cannot equal upper bound",
            function_name
        )));
    }

    let count_f64 = f64::from(count);
    let bucket = if low < high {
        if operand < low {
            0_i64
        } else if operand >= high {
            i64::from(count) + 1
        } else {
            let quotient = if (high - low).is_infinite() {
                (operand / 2.0 - low / 2.0) / (high / 2.0 - low / 2.0)
            } else {
                (operand - low) / (high - low)
            };
            let bucket = (count_f64 * quotient).floor() as i64;
            bucket.min(i64::from(count - 1)) + 1
        }
    } else if operand > low {
        0_i64
    } else if operand <= high {
        i64::from(count) + 1
    } else {
        let quotient = if (low - high).is_infinite() {
            (low / 2.0 - operand / 2.0) / (low / 2.0 - high / 2.0)
        } else {
            (low - operand) / (low - high)
        };
        let bucket = (count_f64 * quotient).floor() as i64;
        bucket.min(i64::from(count - 1)) + 1
    };
    let bucket =
        i32::try_from(bucket).map_err(|_| numeric_value_out_of_range("integer out of range"))?;

    Ok(StoredValue::Int32(bucket))
}

fn eval_width_bucket_numeric_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    fn numeric_value_as_decimal(
        function_name: &str,
        field_name: &str,
        value: StoredValue,
    ) -> Result<Option<BigDecimal>> {
        match value {
            StoredValue::Null => Ok(None),
            StoredValue::Int32(value) => Ok(Some(BigDecimal::from(value))),
            StoredValue::Int64(value) => Ok(Some(BigDecimal::from(value))),
            StoredValue::Numeric(value) => BigDecimal::from_str(&stored_decimal_to_string(&value))
                .map(Some)
                .map_err(|_| Error::Other("DB9 numeric value is out of range".to_owned())),
            other => Err(Error::Other(format!(
                "DB9 function '{}' {} must be numeric, got {}",
                function_name,
                field_name,
                stored_value_pg_display_name(&other)
            ))),
        }
    }
    fn bucket_to_i32(bucket: BigDecimal) -> Result<i32> {
        let bucket = bucket
            .to_i64()
            .ok_or_else(|| numeric_value_out_of_range("integer out of range"))?;
        i32::try_from(bucket).map_err(|_| numeric_value_out_of_range("integer out of range"))
    }

    let mut iter = args.into_iter();
    let operand = numeric_value_as_decimal(function_name, "operand", iter.next().unwrap())?;
    let low = numeric_value_as_decimal(function_name, "lower_bound", iter.next().unwrap())?;
    let high = numeric_value_as_decimal(function_name, "upper_bound", iter.next().unwrap())?;
    let count = integer_value_as_i32(function_name, "count", iter.next().unwrap())?;
    let (Some(operand), Some(low), Some(high), Some(count)) = (operand, low, high, count) else {
        return Ok(StoredValue::Null);
    };

    if count <= 0 {
        return Err(Error::Other(format!(
            "DB9 function '{}' count must be greater than zero",
            function_name
        )));
    }
    if low == high {
        return Err(Error::Other(format!(
            "DB9 function '{}' lower bound cannot equal upper bound",
            function_name
        )));
    }

    let count_decimal = BigDecimal::from(count);
    let bucket = if low < high {
        if operand < low {
            BigDecimal::from(0)
        } else if operand >= high {
            BigDecimal::from(i64::from(count) + 1)
        } else {
            ((operand - low.clone()) * count_decimal / (high - low))
                .to_i64()
                .map(|value| BigDecimal::from(value) + BigDecimal::from(1))
                .ok_or_else(|| numeric_value_out_of_range("integer out of range"))?
        }
    } else if operand > low {
        BigDecimal::from(0)
    } else if operand <= high {
        BigDecimal::from(i64::from(count) + 1)
    } else {
        ((low.clone() - operand) * count_decimal / (low - high))
            .to_i64()
            .map(|value| BigDecimal::from(value) + BigDecimal::from(1))
            .ok_or_else(|| numeric_value_out_of_range("integer out of range"))?
    };

    Ok(StoredValue::Int32(bucket_to_i32(bucket)?))
}

pub(in super::super) fn eval_pi_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if !args.is_empty() {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 0 args, got {}",
            function_name,
            args.len()
        )));
    }
    Ok(StoredValue::Float64(std::f64::consts::PI))
}

fn round_float8_pg(value: f64) -> f64 {
    // PostgreSQL float8 round follows the platform rint/nearbyint tie-to-even rule.
    value.round_ties_even()
}

pub(in super::super) fn eval_round_function(
    function_name: &str,
    args: Vec<StoredValue>,
) -> Result<StoredValue> {
    if args.len() != 1 {
        return Err(Error::Other(format!(
            "DB9 function '{}' expects 1 args, got {}",
            function_name,
            args.len()
        )));
    }

    let mut iter = args.into_iter();
    let value = iter.next().unwrap();

    match value {
        StoredValue::Float64(value) => Ok(StoredValue::Float64(round_float8_pg(value))),
        StoredValue::Int32(value) => Ok(StoredValue::Float64(round_float8_pg(value as f64))),
        StoredValue::Int64(value) => Ok(StoredValue::Float64(round_float8_pg(value as f64))),
        _ => Ok(StoredValue::Null),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pg_like_cbrt_matches_db9_server_formula() {
        assert_eq!(pg_like_cbrt(27.0), 3.0000000000000004);
        assert_eq!(pg_like_cbrt(-8.0), -2.0);
        assert_eq!(pg_like_cbrt(0.0), 0.0);
        assert_eq!(pg_like_cbrt(-0.0).to_bits(), 0.0f64.to_bits());
        assert_ne!(pg_like_cbrt(3.0).to_bits(), 3.0f64.cbrt().to_bits());
    }

    #[test]
    fn shifts_follow_postgresql_wrapping_count_semantics() {
        assert_eq!(
            eval_shift_function(
                "__db9_shl",
                vec![StoredValue::Int32(1), StoredValue::Int32(31)],
                "<<",
                true,
            )
            .unwrap(),
            StoredValue::Int32(1_i32 << 31)
        );
        assert_eq!(
            eval_shift_function(
                "__db9_shl",
                vec![StoredValue::Int32(1), StoredValue::Int32(32)],
                "<<",
                true,
            )
            .unwrap(),
            StoredValue::Int32(1)
        );
        assert_eq!(
            eval_shift_function(
                "__db9_shr",
                vec![StoredValue::Int32(-1), StoredValue::Int32(32)],
                ">>",
                false,
            )
            .unwrap(),
            StoredValue::Int32(-1)
        );
        assert_eq!(
            eval_shift_function(
                "__db9_shl",
                vec![StoredValue::Int64(1), StoredValue::Int32(64)],
                "<<",
                true,
            )
            .unwrap(),
            StoredValue::Int64(1)
        );
        assert_eq!(
            eval_shift_function(
                "__db9_shl",
                vec![StoredValue::Int32(1), StoredValue::Int32(-1)],
                "<<",
                true,
            )
            .unwrap(),
            StoredValue::Int32(i32::MIN)
        );
        assert_eq!(
            eval_shift_function(
                "__db9_shr",
                vec![StoredValue::Int32(1), StoredValue::Int32(-1)],
                ">>",
                false,
            )
            .unwrap(),
            StoredValue::Int32(0)
        );
        assert_eq!(
            eval_shift_function(
                "__db9_shl",
                vec![StoredValue::Int64(1), StoredValue::Int32(-1)],
                "<<",
                true,
            )
            .unwrap(),
            StoredValue::Int64(i64::MIN)
        );
    }
}
