use std::cmp::Eq;
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::{Add, Mul, Sub};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Vector2D<T> {
    x: T,
    y: T,
}

impl<T> Vector2D<T> {
    pub fn new(x: T, y: T) -> Vector2D<T> {
        Vector2D { x, y }
    }

    pub fn x(&self) -> &T {
        &self.x
    }

    pub fn y(&self) -> &T {
        &self.y
    }
}

pub fn index<'a, T, U>(v: &'a [Vec<U>], idx: &Vector2D<T>) -> &'a U
where
    usize: TryFrom<T>,
    <usize as TryFrom<T>>::Error: Debug,
    T: Copy,
{
    let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());
    &v[y][x]
}

pub fn in_range<T, U>(v: &[Vec<U>], idx: &Vector2D<T>) -> bool
where
    usize: TryInto<T>,
    <usize as TryInto<T>>::Error: Debug,
    usize: TryFrom<T>,
    <usize as TryFrom<T>>::Error: Debug,
    T: PartialOrd + Copy,
{
    idx.y >= 0.try_into().unwrap()
        && idx.y < v.len().try_into().unwrap()
        && idx.x >= 0.try_into().unwrap()
        && idx.x
            < v[TryInto::<usize>::try_into(idx.y).unwrap()]
                .len()
                .try_into()
                .unwrap()
}

impl<T: Copy> Vector2D<T> {
    pub fn swap(&self) -> Self {
        Self {
            x: self.y,
            y: self.x,
        }
    }
}

// See: https://github.com/rust-lang/rust/issues/102731
// impl<U: From<T>, T> From<Vector2D<T>> for Vector2D<U> {
//     fn from(value: Vector2D<T>) -> Self {
//         Self {
//             x: U::from(value.x),
//             y: U::from(value.y),
//         }
//     }
// }

impl<T: Add + Add<Output = U>, U> Add for Vector2D<T> {
    type Output = Vector2D<U>;

    fn add(self, rhs: Self) -> Self::Output {
        Vector2D {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

impl<T: Sub + Sub<Output = U>, U> Sub for Vector2D<T> {
    type Output = Vector2D<U>;

    fn sub(self, rhs: Self) -> Self::Output {
        Vector2D {
            x: self.x - rhs.x,
            y: self.y - rhs.y,
        }
    }
}

impl<T: Mul + Mul<Output = U>, U> Mul for Vector2D<T> {
    type Output = Vector2D<U>;

    fn mul(self, rhs: Self) -> Self::Output {
        Vector2D {
            x: self.x * rhs.x,
            y: self.y * rhs.y,
        }
    }
}

impl<T: Mul + Mul<Output = U> + Copy, U> Mul<T> for Vector2D<T> {
    type Output = Vector2D<U>;

    fn mul(self, rhs: T) -> Self::Output {
        Vector2D {
            x: self.x * rhs,
            y: self.y * rhs,
        }
    }
}