use std::f32::consts::PI;

use crate::{Radians, state::DualsenseAxisValue};

/// Represents the value of an axis in the [0; 255] range
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct AxisValue(u8);

impl Default for AxisValue {
    fn default() -> Self {
        Self(127)
    }
}

impl AxisValue {
    /// Returns the ratio in a [0; 1] range
    pub fn as_ratio(&self) -> f32 {
        ((self.0 as f32) / (u8::MAX as f32)).clamp(0.0, 1.0)
    }
}

impl From<DualsenseAxisValue> for AxisValue {
    fn from(val: DualsenseAxisValue) -> Self {
        AxisValue(val.into())
    }
}

impl From<u8> for AxisValue {
    fn from(val: u8) -> Self {
        AxisValue(val)
    }
}

impl From<Radians> for AxisValue {
    /// Converts an angle in the [-PI, PI] range into an AxisValue
    fn from(val: Radians) -> Self {
        let value = ((val.get_angle() / PI) + 0.5) * (u8::MAX as f32);
        AxisValue(value as u8)
    }
}

#[test]
fn axis_value_from_dualsense_value() {
    let ds_values: [DualsenseAxisValue; 5] = [0, 100, 128, 200, 255].map(|v| v.into());
    let expected_values: [AxisValue; 5] = [0, 100, 128, 200, 255].map(|v| v.into());
    let got_values: [AxisValue; 5] = ds_values.map(|v| v.into());

    assert_eq!(expected_values, got_values)
}

#[test]
fn axis_value_from_radians() {
    let radians_values: [Radians; _] =
        [0., PI, PI / 2., PI * 3. / 2., PI * 2., -PI / 2.].map(|v| v.into());
    let expected_values: [AxisValue; _] = [127, 255, 255, 255, 255, 0].map(|v| v.into());
    let got_values: [AxisValue; _] = radians_values.map(|v| v.into());

    assert_eq!(expected_values, got_values)
}
