// 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.

// Constants defined for scalar construction.

use arrow::datatypes::{Decimal32Type, Decimal64Type, Decimal128Type, DecimalType};
use arrow::datatypes::{Decimal256Type, i256};

// Next F16 value above π (upper bound)
pub(super) const PI_UPPER_F16: half::f16 = half::f16::from_bits(0x4249);

// Next f32 value above π (upper bound)
pub(super) const PI_UPPER_F32: f32 = std::f32::consts::PI.next_up();

// Next f64 value above π (upper bound)
pub(super) const PI_UPPER_F64: f64 = std::f64::consts::PI.next_up();

// Next f16 value below -π (lower bound)
pub(super) const NEGATIVE_PI_LOWER_F16: half::f16 = half::f16::from_bits(0xC249);

// Next f32 value below -π (lower bound)
pub(super) const NEGATIVE_PI_LOWER_F32: f32 = (-std::f32::consts::PI).next_down();

// Next f64 value below -π (lower bound)
pub(super) const NEGATIVE_PI_LOWER_F64: f64 = (-std::f64::consts::PI).next_down();

// Next f16 value above π/2 (upper bound)
pub(super) const FRAC_PI_2_UPPER_F16: half::f16 = half::f16::from_bits(0x3E49);

// Next f32 value above π/2 (upper bound)
pub(super) const FRAC_PI_2_UPPER_F32: f32 = std::f32::consts::FRAC_PI_2.next_up();

// Next f64 value above π/2 (upper bound)
pub(super) const FRAC_PI_2_UPPER_F64: f64 = std::f64::consts::FRAC_PI_2.next_up();

// Next f32 value below -π/2 (lower bound)
pub(super) const NEGATIVE_FRAC_PI_2_LOWER_F16: half::f16 = half::f16::from_bits(0xBE49);

// Next f32 value below -π/2 (lower bound)
pub(super) const NEGATIVE_FRAC_PI_2_LOWER_F32: f32 =
    (-std::f32::consts::FRAC_PI_2).next_down();

// Next f64 value below -π/2 (lower bound)
pub(super) const NEGATIVE_FRAC_PI_2_LOWER_F64: f64 =
    (-std::f64::consts::FRAC_PI_2).next_down();

// Generate lookup table for 1 values of decimals (1, 10, 100, etc.)
macro_rules! decimal_ones_lut {
    () => {{
        let mut values = [1; _];
        let mut i = 1;
        while i < values.len() {
            values[i] = values[i - 1] * 10;
            i += 1;
        }
        values
    }};
}

// 1, 10, 100 values meant to be indexed by scale. We omit handling for MAX_SCALE
// itself (we don't go to MAX_SCALE + 1) since we can't represent a 1 value at
// that scale.
pub(super) const DECIMAL32_ONES: [i32; Decimal32Type::MAX_SCALE as usize] =
    decimal_ones_lut!();
pub(super) const DECIMAL64_ONES: [i64; Decimal64Type::MAX_SCALE as usize] =
    decimal_ones_lut!();
pub(super) const DECIMAL128_ONES: [i128; Decimal128Type::MAX_SCALE as usize] =
    decimal_ones_lut!();
pub(super) const DECIMAL256_ONES: [i256; Decimal256Type::MAX_SCALE as usize] = {
    // This code was generated by codex and frankly I don't know how it works,
    // but the test below verifies it outputs the correct values so ¯\_(ツ)_/¯
    //
    // This is mainly a shortcut for not needing to manually list out each value
    // anyway.
    //
    // TODO: simplify this after https://github.com/apache/arrow-rs/pull/10363
    //       lands upstream
    let mut values = [i256::ONE; _];
    let mut i = 1;
    while i < values.len() {
        let (low, high) = values[i - 1].to_parts();
        let low_product = (low as u64 as u128) * 10;
        let high_product = (low >> 64) * 10 + (low_product >> 64);
        let low = ((high_product as u64 as u128) << 64) | low_product as u64 as u128;
        let carry = (high_product >> 64) as i128;
        values[i] = i256::from_parts(low, high * 10 + carry);
        i += 1;
    }
    values
};

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

    #[test]
    fn test_ensure_correct_decimal256_ones() {
        for (scale, val) in DECIMAL256_ONES.iter().enumerate() {
            let zeros = "0".repeat(scale);
            let num = "1".to_string() + &zeros;
            let num = i256::from_string(&num).unwrap();
            assert_eq!(num, *val, "{scale}");
        }
    }
}
