//! The interactable objects of a difficulty.

use crate::{impl_duration, impl_timed};
use loose_enum::loose_enum;
use serde::{Deserialize, Serialize};

/// The standard block/note that a player cuts.
#[doc(alias = "Block")]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(bevy_reflect::Reflect),
    reflect(Debug, Clone, PartialEq)
)]
pub struct Note {
    /// The position of the object in time.
    #[serde(rename = "b")]
    pub beat: f32,
    /// A value representing the vertical position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "y")]
    pub row: i32,
    /// A value representing the horizontal position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "x")]
    pub col: i32,
    /// The color that determines which saber should be used to cut the note.
    #[serde(rename = "c")]
    pub color: NoteColor,
    /// The direction the note should be cut.
    #[serde(rename = "d")]
    pub direction: CutDirection,
    /// The number of degrees counter-clockwise to offset the object by.
    #[serde(rename = "a")]
    pub angle_offset: f32,
}

impl_timed!(Note::beat);

loose_enum! {
    /// The color of a note, which determines which saber should be used to cut it.
    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
    #[cfg_attr(
        feature = "bevy_reflect",
        derive(bevy_reflect::Reflect),
        reflect(Debug, Clone, PartialEq)
    )]
    pub enum NoteColor: i32 {
        #[default]
        Left = 0,
        Right = 1,
    }
}

loose_enum! {
    /// The direction a note should be cut.
    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
    #[cfg_attr(
        feature = "bevy_reflect",
        derive(bevy_reflect::Reflect),
        reflect(Debug, Clone, PartialEq)
    )]
    pub enum CutDirection: i32 {
        #[default]
        Up = 0,
        Down = 1,
        Left = 2,
        Right = 3,
        UpLeft = 4,
        UpRight = 5,
        DownLeft = 6,
        DownRight = 7,
        #[doc(alias = "Dot")]
        Any = 8,
    }
}

impl CutDirection {
    /// Returns the number of degrees a note is rotated, with zero degrees being a downward note.
    ///
    /// Returns zero if the cut direction is undefined/any.
    pub fn get_degrees(&self) -> f32 {
        match self {
            CutDirection::Up => 180.0,
            CutDirection::Down => 0.0,
            CutDirection::Left => -90.0,
            CutDirection::Right => 90.0,
            CutDirection::UpLeft => -135.0,
            CutDirection::UpRight => 135.0,
            CutDirection::DownLeft => -45.0,
            CutDirection::DownRight => 45.0,
            CutDirection::Any => 0.0,
            CutDirection::Undefined(_) => 0.0,
        }
    }
}

/// The spiked bombs that players avoid hitting with their sabers.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(bevy_reflect::Reflect),
    reflect(Debug, Clone, PartialEq)
)]
pub struct Bomb {
    /// The position of the object in time.
    #[serde(rename = "b")]
    pub beat: f32,
    /// A value representing the vertical position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "y")]
    pub row: i32,
    /// A value representing the horizontal position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "x")]
    pub col: i32,
}

impl_timed!(Bomb::beat);

/// A wall/obstacle that players avoid running into.
#[doc(alias = "Obstacle")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(bevy_reflect::Reflect),
    reflect(Debug, Clone, PartialEq)
)]
pub struct Wall {
    /// The start position of the object in time.
    #[serde(rename = "b")]
    pub beat: f32,
    /// The length (in beats) that an object takes place.
    #[serde(rename = "d")]
    pub duration: f32,
    /// A value representing the vertical position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "y")]
    pub row: i32,
    /// A value representing the horizontal position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "x")]
    pub col: i32,
    /// The number of columns that the wall will take up.
    #[serde(rename = "w")]
    pub width: i32,
    /// The number of rows that the wall will take up.
    ///
    /// A standard wall has a height of five while a crouch wall has a height of three.
    #[serde(rename = "h")]
    pub height: i32,
}

impl Default for Wall {
    fn default() -> Self {
        Self {
            beat: 0.0,
            duration: 1.0,
            row: 0,
            col: 0,
            width: 1,
            height: 5,
        }
    }
}

impl_duration!(Wall::beat, duration: duration);

/// A glowing line that guides the player's saber.
#[doc(alias = "Slider")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(bevy_reflect::Reflect),
    reflect(Debug, Clone, PartialEq)
)]
pub struct Arc {
    /// The start position of the object in time.
    #[serde(rename = "b")]
    pub beat: f32,
    /// A value representing the vertical starting position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "y")]
    pub row: i32,
    /// A value representing the horizontal starting position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "x")]
    pub col: i32,
    /// The color of the arc.
    #[serde(rename = "c")]
    pub color: NoteColor,
    /// The direction the arc moves in at the start.
    #[serde(rename = "d")]
    pub direction: CutDirection,
    /// Controls how far away the starting bezier control point is in [cut direction](Self::direction).
    #[serde(rename = "mu")]
    pub control_point: f32,

    /// The end position of the object in time.
    #[serde(rename = "tb")]
    pub tail_beat: f32,
    /// A value representing the vertical ending position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "ty")]
    pub tail_row: i32,
    /// A value representing the horizontal ending position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "tx")]
    pub tail_col: i32,
    /// The direction the arc moves in at the end.
    #[serde(rename = "tc")]
    pub tail_direction: CutDirection,
    /// Controls how far away the ending bezier control point is in [tail cut direction](Self::tail_direction).
    #[serde(rename = "tmu")]
    pub tail_control_point: f32,

    /// Controls how the arc curves from its head to its tail.
    #[serde(rename = "m")]
    pub mid_anchor_mode: MidAnchorMode,
}

impl Default for Arc {
    fn default() -> Self {
        Self {
            beat: 0.0,
            row: 0,
            col: 0,
            color: Default::default(),
            direction: Default::default(),
            control_point: 1.0,
            tail_beat: 1.0,
            tail_row: 0,
            tail_col: 0,
            tail_direction: Default::default(),
            tail_control_point: 1.0,
            mid_anchor_mode: Default::default(),
        }
    }
}

impl_duration!(Arc::beat, end: tail_beat);

loose_enum! {
    /// Controls how an arc curves from its head to its tail.
    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
    #[cfg_attr(
        feature = "bevy_reflect",
        derive(bevy_reflect::Reflect),
        reflect(Debug, Clone, PartialEq)
    )]
    pub enum MidAnchorMode: i32 {
        #[default]
        Straight = 0,
        Clockwise = 1,
        CounterClockwise = 2,
    }
}

/// A chain/burst of mini-notes.
#[doc(alias = "BurstSlider")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(bevy_reflect::Reflect),
    reflect(Debug, Clone, PartialEq)
)]
pub struct Chain {
    /// The start position of the object in time.
    #[serde(rename = "b")]
    pub beat: f32,
    /// A value representing the vertical starting position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "y")]
    pub row: i32,
    /// A value representing the horizontal starting position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "x")]
    pub col: i32,
    /// The color that determines which saber should be used to cut the chain links.
    #[serde(rename = "c")]
    pub color: NoteColor,
    /// The direction the start of the chain should be cut.
    #[serde(rename = "d")]
    pub direction: CutDirection,

    /// The end position of the object in time.
    #[serde(rename = "tb")]
    pub tail_beat: f32,
    /// A value representing the vertical ending position of the object.
    /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
    #[serde(rename = "ty")]
    pub tail_row: i32,
    /// A value representing the horizontal ending position of the object.
    /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
    #[serde(rename = "tx")]
    pub tail_col: i32,

    /// The number of links the chain has, including the connected [`Note`].
    #[serde(rename = "sc")]
    pub link_count: i32,
    /// The percent of the path (from head to tail) that will be used, usually in the range 0..1 inclusive.
    /// Smaller values will result in the chain links bunching up near the head.
    ///
    /// Setting this to zero will crash the game.
    #[serde(rename = "s")]
    pub link_squish: f32,
}

impl Default for Chain {
    fn default() -> Self {
        Self {
            beat: 0.0,
            row: 1,
            col: 0,
            color: Default::default(),
            direction: Default::default(),
            tail_beat: 0.0,
            tail_row: 0,
            tail_col: 0,
            link_count: 3,
            link_squish: 1.0,
        }
    }
}

impl_duration!(Chain::beat, end: tail_beat);
