//! Common options -- mostly those related to [`crate::SampleSettingsFile`] and
//! [`crate::projects::slots::SlotAttributes`] types.

use crate::{OptionEnumValueConvert, OtToolsIoErrors, RBoxErr};
use serde::{Deserialize, Serialize};

/// Sample Timestrech options.
/// See Octatrack Manaul section 13.2.4 ATTRIBUTES
#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, Copy, Hash, Eq)]
pub enum TimestrechMode {
    /// No timestreching applied.
    Off = 0,

    /// Regular timestreching.
    #[default]
    Normal = 2,

    /// Drum / Rythmic specific algorithm
    Beat = 3,
}

impl OptionEnumValueConvert<u32> for TimestrechMode {
    fn from_value(v: &u32) -> RBoxErr<Self> {
        match v {
            0 => Ok(TimestrechMode::Off),
            2 => Ok(TimestrechMode::Normal),
            3 => Ok(TimestrechMode::Beat),
            _ => Err(OtToolsIoErrors::NoMatchingOptionEnumValue.into()),
        }
    }

    fn value(&self) -> RBoxErr<u32> {
        Ok(*self as u32)
    }
}

/// Sample attributes Loop mode options.
/// See Octatrack Manaul section 13.2.4 ATTRIBUTES

#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, Copy, Hash, Eq)]
pub enum LoopMode {
    /// Loop points are ignored and sample will never loop.
    #[default]
    Off = 0,

    /// Loop by starting again at the loop start position once playback of the sample reaches loop end.
    Normal = 1,

    /// Loop by continuously reversing once playback of the sample reaches loop end/loop start.
    PingPong = 2,
}

impl OptionEnumValueConvert<u32> for LoopMode {
    fn from_value(v: &u32) -> RBoxErr<Self> {
        match v {
            0 => Ok(LoopMode::Off),
            1 => Ok(LoopMode::Normal),
            2 => Ok(LoopMode::PingPong),
            _ => Err(OtToolsIoErrors::NoMatchingOptionEnumValue.into()),
        }
    }

    fn value(&self) -> RBoxErr<u32> {
        Ok(*self as u32)
    }
}

/// Sample attributes Trig Quantization options
/// (quantization when manually triggering samples via track buttons).
/// See Octatrack Manaul section 13.2.4 ATTRIBUTES

#[derive(PartialEq, Debug, Clone, Default, Serialize, Deserialize, Copy, Hash, Eq)]
#[repr(u32)]
pub enum TrigQuantizationMode {
    /// Play back immediately, no quantization.
    #[default]
    Direct = 255,

    /// Play once the pattern ends
    PatternLength = 0,

    /// Play after 1 sequencer step(s).
    OneStep = 1,

    /// Play after 2 sequencer step(s).
    TwoSteps = 2,

    /// Play after 3 sequencer step(s).
    ThreeSteps = 3,

    /// Play after 4 sequencer step(s).
    FourSteps = 4,

    /// Play after 6 sequencer step(s).
    SixSteps = 5,

    /// Play after 8 sequencer step(s).
    EightSteps = 6,

    /// Play after 12 sequencer step(s).
    TwelveSteps = 7,

    /// Play after 16 sequencer step(s).
    SixteenSteps = 8,

    /// Play after 24 sequencer step(s).
    TwentyFourSteps = 9,

    /// Play after 32 sequencer step(s).
    ThirtyTwoSteps = 10,

    /// Play after 48 sequencer step(s).
    FourtyEightSteps = 11,

    /// Play after 64 sequencer step(s).
    SixtyFourSteps = 12,

    /// Play after 96 sequencer step(s).
    NinetySixSteps = 13,

    /// Play after 128 sequencer step(s).
    OneTwentyEightSteps = 14,

    /// Play after 192 sequencer step(s).
    OneNinetyTwoSteps = 15,

    /// Play after 256 sequencer step(s).
    TwoFiveSixSteps = 16,
}

impl OptionEnumValueConvert<u32> for TrigQuantizationMode {
    fn from_value(v: &u32) -> RBoxErr<Self> {
        match v {
            255 => Ok(TrigQuantizationMode::Direct),
            0 => Ok(TrigQuantizationMode::PatternLength),
            1 => Ok(TrigQuantizationMode::OneStep),
            2 => Ok(TrigQuantizationMode::TwoSteps),
            3 => Ok(TrigQuantizationMode::ThreeSteps),
            4 => Ok(TrigQuantizationMode::FourSteps),
            5 => Ok(TrigQuantizationMode::SixSteps),
            6 => Ok(TrigQuantizationMode::EightSteps),
            7 => Ok(TrigQuantizationMode::TwelveSteps),
            8 => Ok(TrigQuantizationMode::SixteenSteps),
            9 => Ok(TrigQuantizationMode::TwentyFourSteps),
            10 => Ok(TrigQuantizationMode::ThirtyTwoSteps),
            11 => Ok(TrigQuantizationMode::FourtyEightSteps),
            12 => Ok(TrigQuantizationMode::SixtyFourSteps),
            13 => Ok(TrigQuantizationMode::NinetySixSteps),
            14 => Ok(TrigQuantizationMode::OneTwentyEightSteps),
            15 => Ok(TrigQuantizationMode::OneNinetyTwoSteps),
            16 => Ok(TrigQuantizationMode::TwoFiveSixSteps),
            _ => Err(OtToolsIoErrors::NoMatchingOptionEnumValue.into()),
        }
    }

    fn value(&self) -> RBoxErr<u32> {
        Ok(*self as u32)
    }
}

/// "Specification" tests ... ie. guarantee that enum values match correct values.
#[cfg(test)]
#[allow(unused_imports)]
mod test_spec {

    mod ot_trig_quantize_mode {

        mod value {
            use crate::OptionEnumValueConvert;
            use crate::TrigQuantizationMode;

            #[test]
            fn test_direct() {
                assert_eq!(TrigQuantizationMode::Direct.value().unwrap(), 255);
            }
            #[test]
            fn test_patternlen() {
                assert_eq!(TrigQuantizationMode::PatternLength.value().unwrap(), 0);
            }
            #[test]
            fn test_1() {
                assert_eq!(TrigQuantizationMode::OneStep.value().unwrap(), 1);
            }
            #[test]
            fn test_2() {
                assert_eq!(TrigQuantizationMode::TwoSteps.value().unwrap(), 2);
            }
            #[test]
            fn test_3() {
                assert_eq!(TrigQuantizationMode::ThreeSteps.value().unwrap(), 3);
            }
            #[test]
            fn test_4() {
                assert_eq!(TrigQuantizationMode::FourSteps.value().unwrap(), 4);
            }
            #[test]
            fn test_6() {
                assert_eq!(TrigQuantizationMode::SixSteps.value().unwrap(), 5);
            }
            #[test]
            fn test_8() {
                assert_eq!(TrigQuantizationMode::EightSteps.value().unwrap(), 6);
            }
            #[test]
            fn test_12() {
                assert_eq!(TrigQuantizationMode::TwelveSteps.value().unwrap(), 7);
            }
            #[test]
            fn test_16() {
                assert_eq!(TrigQuantizationMode::SixteenSteps.value().unwrap(), 8);
            }
            #[test]
            fn test_24() {
                assert_eq!(TrigQuantizationMode::TwentyFourSteps.value().unwrap(), 9);
            }
            #[test]
            fn test_32() {
                assert_eq!(TrigQuantizationMode::ThirtyTwoSteps.value().unwrap(), 10);
            }
            #[test]
            fn test_48() {
                assert_eq!(TrigQuantizationMode::FourtyEightSteps.value().unwrap(), 11);
            }
            #[test]
            fn test_64() {
                assert_eq!(TrigQuantizationMode::SixtyFourSteps.value().unwrap(), 12);
            }
            #[test]
            fn test_96() {
                assert_eq!(TrigQuantizationMode::NinetySixSteps.value().unwrap(), 13);
            }
            #[test]
            fn test_128() {
                assert_eq!(
                    TrigQuantizationMode::OneTwentyEightSteps.value().unwrap(),
                    14
                );
            }
            #[test]
            fn test_192() {
                assert_eq!(TrigQuantizationMode::OneNinetyTwoSteps.value().unwrap(), 15);
            }
            #[test]
            fn test_256() {
                assert_eq!(TrigQuantizationMode::TwoFiveSixSteps.value().unwrap(), 16);
            }
        }

        mod from_value {
            use crate::OptionEnumValueConvert;
            use crate::TrigQuantizationMode;

            #[test]
            fn test_error() {
                assert!(TrigQuantizationMode::from_value(&200).is_err());
            }
            #[test]
            fn test_direct() {
                assert_eq!(
                    TrigQuantizationMode::Direct,
                    TrigQuantizationMode::from_value(&255).unwrap()
                );
            }
            #[test]
            fn test_patternlen() {
                assert_eq!(
                    TrigQuantizationMode::PatternLength,
                    TrigQuantizationMode::from_value(&0).unwrap()
                );
            }
            #[test]
            fn test_1() {
                assert_eq!(
                    TrigQuantizationMode::OneStep,
                    TrigQuantizationMode::from_value(&1).unwrap()
                );
            }
            #[test]
            fn test_2() {
                assert_eq!(
                    TrigQuantizationMode::TwoSteps,
                    TrigQuantizationMode::from_value(&2).unwrap()
                );
            }
            #[test]
            fn test_3() {
                assert_eq!(
                    TrigQuantizationMode::ThreeSteps,
                    TrigQuantizationMode::from_value(&3).unwrap()
                );
            }
            #[test]
            fn test_4() {
                assert_eq!(
                    TrigQuantizationMode::FourSteps,
                    TrigQuantizationMode::from_value(&4).unwrap()
                );
            }
            #[test]
            fn test_6() {
                assert_eq!(
                    TrigQuantizationMode::SixSteps,
                    TrigQuantizationMode::from_value(&5).unwrap()
                );
            }
            #[test]
            fn test_8() {
                assert_eq!(
                    TrigQuantizationMode::EightSteps,
                    TrigQuantizationMode::from_value(&6).unwrap()
                );
            }
            #[test]
            fn test_12() {
                assert_eq!(
                    TrigQuantizationMode::TwelveSteps,
                    TrigQuantizationMode::from_value(&7).unwrap()
                );
            }
            #[test]
            fn test_16() {
                assert_eq!(
                    TrigQuantizationMode::SixteenSteps,
                    TrigQuantizationMode::from_value(&8).unwrap()
                );
            }
            #[test]
            fn test_24() {
                assert_eq!(
                    TrigQuantizationMode::TwentyFourSteps,
                    TrigQuantizationMode::from_value(&9).unwrap()
                );
            }
            #[test]
            fn test_32() {
                assert_eq!(
                    TrigQuantizationMode::ThirtyTwoSteps,
                    TrigQuantizationMode::from_value(&10).unwrap()
                );
            }
            #[test]
            fn test_48() {
                assert_eq!(
                    TrigQuantizationMode::FourtyEightSteps,
                    TrigQuantizationMode::from_value(&11).unwrap()
                );
            }
            #[test]
            fn test_64() {
                assert_eq!(
                    TrigQuantizationMode::SixtyFourSteps,
                    TrigQuantizationMode::from_value(&12).unwrap()
                );
            }
            #[test]
            fn test_96() {
                assert_eq!(
                    TrigQuantizationMode::NinetySixSteps,
                    TrigQuantizationMode::from_value(&13).unwrap()
                );
            }
            #[test]
            fn test_128() {
                assert_eq!(
                    TrigQuantizationMode::OneTwentyEightSteps,
                    TrigQuantizationMode::from_value(&14).unwrap()
                );
            }
            #[test]
            fn test_192() {
                assert_eq!(
                    TrigQuantizationMode::OneNinetyTwoSteps,
                    TrigQuantizationMode::from_value(&15).unwrap()
                );
            }
            #[test]
            fn test_256() {
                assert_eq!(
                    TrigQuantizationMode::TwoFiveSixSteps,
                    TrigQuantizationMode::from_value(&16).unwrap()
                );
            }
        }
    }

    mod ot_timestrech_mode {

        mod value {
            use crate::OptionEnumValueConvert;
            use crate::TimestrechMode;

            #[test]
            fn test_off_value() {
                assert_eq!(TimestrechMode::Off.value().unwrap(), 0);
            }
            #[test]
            fn test_normal_value() {
                assert_eq!(TimestrechMode::Normal.value().unwrap(), 2);
            }
            #[test]
            fn test_beat_value() {
                assert_eq!(TimestrechMode::Beat.value().unwrap(), 3);
            }
        }

        mod from_value {
            use crate::OptionEnumValueConvert;
            use crate::TimestrechMode;

            #[test]
            fn test_error() {
                // not in a sequental range with other values
                // dunno why they implemented it to skip value of 1, possible bug or easter egg?
                assert!(TimestrechMode::from_value(&1).is_err());
                // do a slightly exhausitve check, but don't test the whole u32 range
                // as it's not worth the performance drain
                for i in 4..u8::MAX {
                    let b = i as u32;
                    assert!(TimestrechMode::from_value(&b).is_err());
                }
            }
            #[test]
            fn test_off_from_value() {
                assert_eq!(TimestrechMode::Off, TimestrechMode::from_value(&0).unwrap());
            }
            #[test]
            fn test_normal_from_value() {
                assert_eq!(
                    TimestrechMode::Normal,
                    TimestrechMode::from_value(&2).unwrap()
                );
            }
            #[test]
            fn test_beat_from_value() {
                assert_eq!(
                    TimestrechMode::Beat,
                    TimestrechMode::from_value(&3).unwrap()
                );
            }
        }
    }

    mod ot_loop_mode {

        mod value {
            use crate::LoopMode;
            use crate::OptionEnumValueConvert;

            #[test]
            fn test_off_value() {
                assert_eq!(LoopMode::Off.value().unwrap(), 0);
            }
            #[test]
            fn test_normal_value() {
                assert_eq!(LoopMode::Normal.value().unwrap(), 1);
            }
            #[test]
            fn test_beat_value() {
                assert_eq!(LoopMode::PingPong.value().unwrap(), 2);
            }
        }

        mod from_value {
            use crate::LoopMode;
            use crate::OptionEnumValueConvert;

            #[test]
            fn test_error() {
                // do a slightly exhausitve check, but don't test the whole u32 range
                // as it's not worth the performance drain
                for i in 3..u8::MAX {
                    let x = i as u32;
                    assert!(LoopMode::from_value(&x).is_err());
                }
            }
            #[test]
            fn test_off_from_value() {
                assert_eq!(LoopMode::Off, LoopMode::from_value(&0).unwrap());
            }
            #[test]
            fn test_normal_from_value() {
                assert_eq!(LoopMode::Normal, LoopMode::from_value(&1).unwrap());
            }
            #[test]
            fn test_beat_from_value() {
                assert_eq!(LoopMode::PingPong, LoopMode::from_value(&2).unwrap());
            }
        }
    }
}
