use std::{fmt, str::FromStr};

#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub enum Orientation {
    Normal,
    RightUp,
    BottomUp,
    LeftUp,
}

#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub enum Rotation {
    Clockwise,
    AntiClockwise,
    Flip,
}

impl Orientation {
    const ORIENTATIONS: [Orientation; 4] = [
        Orientation::Normal,
        Orientation::RightUp,
        Orientation::BottomUp,
        Orientation::LeftUp,
    ];

    pub fn get_rotation_to(&self, new: Self) -> Option<Rotation> {
        use Orientation::*;
        match (*self,new) {
            // anticlockwise 90 degree turn
            (Normal,RightUp) 
            | (RightUp,BottomUp) 
            | (BottomUp,LeftUp) 
            | (LeftUp,Normal) =>
                Some(Rotation::AntiClockwise),

            // 180 degree turn
            (Normal,BottomUp) 
            | (RightUp,LeftUp) 
            | (BottomUp,Normal) 
            | (LeftUp,RightUp) =>
                Some(Rotation::Flip),

            // clockwise 90 degree turn
            (Normal,LeftUp) 
            | (RightUp,Normal) 
            | (BottomUp,RightUp) 
            | (LeftUp,BottomUp) =>
                Some(Rotation::Clockwise),

            _ => None,
        }
    }
    
    pub fn get_orientation(vec: (f32,f32),threshold: f32) -> Option<Self> {
        Self::ORIENTATIONS.iter().map(|orientation| {
            let tmp_vec = orientation.get_vec();
            let dist = (vec.0-tmp_vec.0).powi(2)
                + (vec.1-tmp_vec.1).powi(2);
            (orientation.clone(),dist)
        })
        .find(|(_,dist)| *dist < threshold )
        .map(|(orientation,_)| orientation )
    }

    pub fn get_vec(&self) -> (f32,f32) {
        use Orientation::*;
        match self {
            Normal      => ( 0.,-1.),
            RightUp     => (-1., 0.),
            BottomUp    => ( 0., 1.),
            LeftUp      => ( 1., 0.),
        }
    }
}

impl fmt::Display for Orientation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Orientation::*;
        match self {
            Normal      => write!(f,"normal"),
            RightUp     => write!(f,"90"),
            BottomUp    => write!(f,"180"),
            LeftUp      => write!(f,"270"),
        }
    }
}

impl FromStr for Orientation {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use Orientation::*;
        match s {
            "normal" => Ok(Normal),
            "90"     => Ok(RightUp),
            "180"    => Ok(BottomUp),
            "270"    => Ok(LeftUp),
            _        => Err("Invalid Orientation".to_string()),
        }
    }
}
