use std::{
    cmp,
    ops::{Add, Sub},
};

pub trait Cast<T> {
    fn cast(self) -> T;
}

pub struct Rectangle<T> {
    pub x: T,
    pub y: T,
    pub w: T,
    pub h: T,
}

pub struct Point<T> {
    pub x: T,
    pub y: T,
}

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

impl<U, T: Cast<U>> Cast<Point<U>> for Point<T> {
    fn cast(self) -> Point<U> {
        Point {
            x: self.x.cast(),
            y: self.y.cast(),
        }
    }
}

impl<T> Rectangle<T>
where
    T: Copy,
{
    pub fn new(x: T, y: T, w: T, h: T) -> Self {
        Self { x, y, w, h }
    }

    pub fn from_origin_size(origin: Point<T>, w: T, h: T) -> Self {
        Self {
            x: origin.x,
            y: origin.y,
            w,
            h,
        }
    }

    pub fn origin(&self) -> Point<T> {
        Point {
            x: self.x,
            y: self.y,
        }
    }

    pub fn contains(&self, point: Point<T>) -> bool
    where
        T: Ord + Sub<Output = T>,
    {
        point.x >= self.x
            && point.y >= self.y
            && point.x - self.x < self.w
            && point.y - self.y < self.h
    }

    pub fn intersection(&self, other: &Self) -> Option<Self>
    where
        T: Ord + Add<Output = T> + Sub<Output = T>,
    {
        let ax1 = self.x;
        let ay1 = self.y;
        let ax2 = self.x + self.w;
        let ay2 = self.y + self.h;

        let bx1 = other.x;
        let by1 = other.y;
        let bx2 = other.x + other.w;
        let by2 = other.y + other.h;

        let ix1 = cmp::max(ax1, bx1);
        let iy1 = cmp::max(ay1, by1);
        let ix2 = cmp::min(ax2, bx2);
        let iy2 = cmp::min(ay2, by2);

        if ix2 <= ix1 || iy2 <= iy1 {
            None
        } else {
            let iw = ix2 - ix1;
            let ih = iy2 - iy1;

            Some(Rectangle {
                x: ix1,
                y: iy1,
                w: iw,
                h: ih,
            })
        }
    }
}

impl<U, T: Cast<U>> Cast<Rectangle<U>> for Rectangle<T> {
    fn cast(self) -> Rectangle<U> {
        Rectangle {
            x: self.x.cast(),
            y: self.y.cast(),
            w: self.w.cast(),
            h: self.h.cast(),
        }
    }
}

macro_rules! impl_primitive_cast {
    ($($t:ty => $u:ty),+ $(,)?) => {
        $(
            impl Cast<$u> for $t {
                fn cast(self) -> $u {
                    self as $u
                }
            }
        )+
    };
}

impl_primitive_cast!(
    u8 => u16,
    u8 => u32,
    u8 => u64,
    u8 => usize,

    u16 => u8,
    u16 => u32,
    u16 => u64,
    u16 => usize,

    u32 => u8,
    u32 => u16,
    u32 => u64,
    u32 => usize,

    u64 => u8,
    u64 => u16,
    u64 => u32,
    u64 => usize,

    usize => u8,
    usize => u16,
    usize => u32,
    usize => u64,
);
