use std::{
    array,
    iter::Sum,
    ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign},
    str::FromStr,
};

use num_traits::{one, zero, One, Zero};
use strum::EnumIter;
use thiserror::Error;

pub trait Abs {
    type Output;
    fn abs(self) -> Self::Output;
}

macro_rules! impl_abs_signed {
    ($($t:ty)*) => {
        $(impl Abs for $t {
            type Output = $t;
            fn abs(self) -> Self {
                self.abs()
            }
        })*
    };
}
impl_abs_signed!(i8 i16 i32 i64 i128 f32 f64);

macro_rules! impl_abs_noop {
    ($($t:ty)*) => {
        $(impl Abs for $t {
            type Output = $t;
            fn abs(self) -> Self {
                self
            }
        })*
    };
}
impl_abs_noop!(u8 u16 u32 u64 u128);

/// A vector in `N`-space.
///
/// The coordinate system is canonically left-handed:
///
/// Axis | +       | -
/// -----|---------|------
/// x    | right   | left
/// y    | up      | down
/// z    | forward | back
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VecN<const N: usize, T> {
    coords: [T; N],
}

impl<T> VecN<2, T> {
    #[must_use]
    pub const fn new(x: T, y: T) -> Self {
        Self { coords: [x, y] }
    }
}

impl<T> VecN<3, T> {
    #[must_use]
    pub const fn new(x: T, y: T, z: T) -> Self {
        Self { coords: [x, y, z] }
    }
}

impl<const N: usize, T> VecN<N, T> {
    #[must_use]
    pub fn map<U>(self, f: impl FnMut(T) -> U) -> VecN<N, U> {
        VecN {
            coords: self.coords.map(f),
        }
    }

    pub fn try_map<U, E>(self, mut f: impl FnMut(T) -> Result<U, E>) -> Result<VecN<N, U>, E>
    where
        [U; N]: Default,
    {
        let mut coords: [U; N] = Default::default();
        for (coord, x) in coords.iter_mut().zip(self.coords) {
            *coord = f(x)?;
        }
        Ok(VecN { coords })
    }

    #[must_use]
    pub fn len(self) -> f64
    where
        T: Into<f64>,
    {
        self.coords
            .into_iter()
            .map(|l| l.into().powi(2))
            .sum::<f64>()
            .sqrt()
    }

    #[must_use]
    pub fn manhattan_len<U>(self) -> U
    where
        T: Abs<Output = U>,
        U: Sum,
    {
        self.coords.into_iter().map(Abs::abs).sum()
    }
}

impl<const N: usize, T> Add for VecN<N, T>
where
    T: Add<Output = T>,
{
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        let mut coords = self.coords.into_iter().zip(rhs.coords).map(|(l, r)| l + r);
        Self {
            coords: array::from_fn(|_| coords.next().unwrap()),
        }
    }
}

impl<const N: usize, T> AddAssign for VecN<N, T>
where
    T: AddAssign,
{
    fn add_assign(&mut self, rhs: Self) {
        for (l, r) in self.coords.iter_mut().zip(rhs.coords) {
            *l += r;
        }
    }
}

impl<const N: usize, T> Sub for VecN<N, T>
where
    T: Sub<Output = T>,
{
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        let mut coords = self.coords.into_iter().zip(rhs.coords).map(|(l, r)| l - r);
        Self {
            coords: array::from_fn(|_| coords.next().unwrap()),
        }
    }
}

impl<const N: usize, T> SubAssign for VecN<N, T>
where
    T: SubAssign,
{
    fn sub_assign(&mut self, rhs: Self) {
        for (l, r) in self.coords.iter_mut().zip(rhs.coords) {
            *l -= r;
        }
    }
}

impl<const N: usize, T, U> Mul<U> for VecN<N, T>
where
    T: Mul<U, Output = T>,
    U: Copy,
{
    type Output = Self;

    fn mul(self, n: U) -> Self::Output {
        Self {
            coords: self.coords.map(|c| c * n),
        }
    }
}

/*
impl<const N: usize, T> Mul<VecN<N, T>> for T
where
    T: Mul<Output = T>,
{
    type Output = VecN<N, T>;

    fn mul(self, vec: VecN<N, T>) -> Self::Output {
        VecN {
            coords: vec.coords.map(|c| c * self),
        }
    }
}
*/

impl<const N: usize, T> From<[T; N]> for VecN<N, T> {
    fn from(coords: [T; N]) -> Self {
        Self { coords }
    }
}

impl<const N: usize, T> From<VecN<N, T>> for [T; N] {
    fn from(v: VecN<N, T>) -> Self {
        v.coords
    }
}

impl<T> From<Direction2> for VecN<2, T>
where
    T: Zero + One + Neg<Output = T>,
{
    /// Creates a unit vector in the given direction.
    fn from(dir: Direction2) -> Self {
        let z = zero();
        let o = one();

        match dir {
            Direction2::Right => Self::new(o, z),
            Direction2::Left => Self::new(-o, z),
            Direction2::Up => Self::new(z, o),
            Direction2::Down => Self::new(z, -o),
        }
    }
}

impl<T> From<Direction3> for VecN<3, T>
where
    T: Zero + One + Neg<Output = T> + Copy,
{
    /// Creates a unit vector in the given direction.
    fn from(dir: Direction3) -> Self {
        let z = zero();
        let o = one();

        match dir {
            Direction3::Right => Self::new(o, z, z),
            Direction3::Left => Self::new(-o, z, z),
            Direction3::Up => Self::new(z, o, z),
            Direction3::Down => Self::new(z, -o, z),
            Direction3::Forward => Self::new(z, z, o),
            Direction3::Back => Self::new(z, z, -o),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum ParseVecError<E> {
    #[error("failed to parse axis {axis}")]
    AxisParseError {
        axis: usize,
        #[source]
        source: E,
    },
    #[error("not enough dimensions specified, expected {expected}")]
    NotEnoughDimensions { expected: usize },
    #[error("too many dimensions specified, expected {expected}")]
    TooManyDimensions { expected: usize },
}

impl<const N: usize, T> FromStr for VecN<N, T>
where
    [T; N]: Default,
    T: FromStr,
{
    type Err = ParseVecError<T::Err>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut parts = s.split(',');

        let mut coords: [T; N] = Default::default();
        for (i, axis) in coords.iter_mut().enumerate() {
            *axis = parts
                .next()
                .ok_or(ParseVecError::NotEnoughDimensions { expected: N })?
                .parse()
                .map_err(|e| ParseVecError::AxisParseError { axis: i, source: e })?;
        }

        if parts.next().is_some() {
            return Err(ParseVecError::TooManyDimensions { expected: N });
        }

        Ok(Self { coords })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIter)]
pub enum Direction2 {
    Right,
    Left,
    Up,
    Down,
}

impl Neg for Direction2 {
    type Output = Self;

    fn neg(self) -> Self::Output {
        match self {
            Direction2::Right => Direction2::Left,
            Direction2::Left => Direction2::Right,
            Direction2::Up => Direction2::Down,
            Direction2::Down => Direction2::Up,
        }
    }
}

impl FromStr for Direction2 {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "R" => Ok(Direction2::Right),
            "L" => Ok(Direction2::Left),
            "U" => Ok(Direction2::Up),
            "D" => Ok(Direction2::Down),
            _ => Err(()),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIter)]
pub enum Direction3 {
    Right = 0,
    Left = 1,
    Up = 2,
    Down = 4,
    Forward = 5,
    Back = 6,
}

impl From<Direction2> for Direction3 {
    fn from(dir: Direction2) -> Self {
        match dir {
            Direction2::Right => Direction3::Right,
            Direction2::Left => Direction3::Left,
            Direction2::Up => Direction3::Up,
            Direction2::Down => Direction3::Down,
        }
    }
}

impl Neg for Direction3 {
    type Output = Self;

    fn neg(self) -> Self::Output {
        match self {
            Direction3::Right => Direction3::Left,
            Direction3::Left => Direction3::Right,
            Direction3::Up => Direction3::Down,
            Direction3::Down => Direction3::Up,
            Direction3::Forward => Direction3::Back,
            Direction3::Back => Direction3::Forward,
        }
    }
}

impl FromStr for Direction3 {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "R" => Ok(Direction3::Right),
            "L" => Ok(Direction3::Left),
            "U" => Ok(Direction3::Up),
            "D" => Ok(Direction3::Down),
            "F" => Ok(Direction3::Forward),
            "B" => Ok(Direction3::Back),
            _ => Err(()),
        }
    }
}
