use std::error::Error;
use std::str::FromStr;
use std::{cmp, ops};

use smithay::utils::{Coordinate, Point, Rectangle, Size};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Matrix3x3<T: Copy> {
    data: [T; 9],
}

impl<T: Copy + Default> TryFrom<Vec<T>> for Matrix3x3<T> {
    type Error = Box<dyn Error>;

    fn try_from(v: Vec<T>) -> Result<Self, Self::Error> {
        v.try_into()
            .map(|data| Self { data })
            .map_err(|v: Vec<T>| format!("Matrix3x3 requires 9 elements, got {}", v.len()).into())
    }
}

impl ops::Mul<Vector3D<f32>> for &Matrix3x3<f32> {
    type Output = Vector3D<f32>;

    fn mul(self, v: Vector3D<f32>) -> Self::Output {
        let [m0, m1, m2, m3, m4, m5, m6, m7, m8] = self.data;
        Vector3D {
            x: m0 * v.x + m1 * v.y + m2 * v.z,
            y: m3 * v.x + m4 * v.y + m5 * v.z,
            z: m6 * v.x + m7 * v.y + m8 * v.z,
        }
    }
}

impl<T: FromStr + Copy + Default> FromStr for Matrix3x3<T>
where
    <T as FromStr>::Err: Error + 'static,
{
    type Err = Box<dyn Error>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let elements: Vec<T> = s
            .split(';')
            .take(3)
            .flat_map(|row| row.split(',').take(3))
            .map(|ch| T::from_str(ch.trim()))
            .collect::<Result<_, _>>()?;

        Self::try_from(elements)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Vector3D<T> {
    pub x: T,
    pub y: T,
    pub z: T,
}

impl<T> Vector3D<T> {
    #[inline]
    pub const fn new(x: T, y: T, z: T) -> Self {
        Self { x, y, z }
    }
}

impl Vector for Vector3D<f32> {
    fn scale(&self, s: f64) -> Self {
        let s = s as f32;
        Self { x: self.x * s, y: self.y * s, z: self.z * s }
    }

    fn min(&self, other: impl Into<Self>) -> Self {
        let o = other.into();
        Self { x: self.x.min(o.x), y: self.y.min(o.y), z: self.z.min(o.z) }
    }

    fn max(&self, other: impl Into<Self>) -> Self {
        let o = other.into();
        Self { x: self.x.max(o.x), y: self.y.max(o.y), z: self.z.max(o.z) }
    }

    fn sub(&self, other: impl Into<Self>) -> Self {
        let o = other.into();
        Self { x: self.x - o.x, y: self.y - o.y, z: self.z - o.z }
    }
}

#[allow(unused)]
pub trait Vector: Sized {
    #[must_use]
    fn scale(&self, scale: f64) -> Self;

    #[must_use]
    fn min(&self, other: impl Into<Self>) -> Self;

    #[must_use]
    fn max(&self, other: impl Into<Self>) -> Self;

    #[must_use]
    fn sub(&self, other: impl Into<Self>) -> Self;
}

pub trait IntoVector {
    fn as_vector(&self) -> (i32, i32);
}

impl<K> IntoVector for Point<i32, K> {
    fn as_vector(&self) -> (i32, i32) {
        (self.x, self.y)
    }
}

impl<K> IntoVector for Size<i32, K> {
    fn as_vector(&self) -> (i32, i32) {
        (self.w, self.h)
    }
}

impl<T> Vector for T
where
    T: IntoVector + From<(i32, i32)>,
{
    fn scale(&self, scale: f64) -> Self {
        let (a, b) = self.as_vector();
        Self::from(((a as f64 * scale).round() as i32, (b as f64 * scale).round() as i32))
    }

    fn min(&self, other: impl Into<Self>) -> Self {
        let (a, b) = self.as_vector();
        let (c, d) = other.into().as_vector();
        Self::from((cmp::min(a, c), cmp::min(b, d)))
    }

    fn max(&self, other: impl Into<Self>) -> Self {
        let (a, b) = self.as_vector();
        let (c, d) = other.into().as_vector();
        Self::from((cmp::max(a, c), cmp::max(b, d)))
    }

    fn sub(&self, other: impl Into<Self>) -> Self {
        let (a, b) = self.as_vector();
        let (c, d) = other.into().as_vector();
        Self::from((a - c, b - d))
    }
}

/// Fit `rect` inside `bounds`: shrink it to the bounds size when larger,
/// then pull the origin back so the whole rect lies within.
pub fn clamp_rect<K>(rect: Rectangle<i32, K>, bounds: Rectangle<i32, K>) -> Rectangle<i32, K> {
    let bw = cmp::max(bounds.size.w, 1);
    let bh = cmp::max(bounds.size.h, 1);
    let w = rect.size.w.clamp(1, bw);
    let h = rect.size.h.clamp(1, bh);
    let x = rect.loc.x.clamp(bounds.loc.x, bounds.loc.x + bw - w);
    let y = rect.loc.y.clamp(bounds.loc.y, bounds.loc.y + bh - h);
    Rectangle::new(Point::from((x, y)), Size::from((w, h)))
}

pub trait SubtractRectFast<N, K> {
    fn subtract_rect(&mut self, sub: Rectangle<N, K>);
}

impl<N: Coordinate, K> SubtractRectFast<N, K> for Vec<Rectangle<N, K>> {
    fn subtract_rect(&mut self, sub: Rectangle<N, K>) {
        let len = self.len();

        for i in 0..len {
            let rect = self[i];

            if !rect.overlaps(sub) {
                self.push(rect);
                continue;
            }

            if sub.contains_rect(rect) {
                continue;
            }

            let overlap = match rect.intersection(sub) {
                Some(o) => o,
                None => {
                    self.push(rect);
                    continue;
                },
            };

            let mut emit = |r: Rectangle<N, K>| {
                if !r.is_empty() {
                    self.push(r);
                }
            };

            let top_h = overlap.loc.y.saturating_sub(rect.loc.y);
            emit(Rectangle::new(rect.loc, (rect.size.w, top_h).into()));

            let left_w = overlap.loc.x.saturating_sub(rect.loc.x);
            emit(Rectangle::new(
                (rect.loc.x, overlap.loc.y).into(),
                (left_w, overlap.size.h).into(),
            ));

            let right_x = overlap.loc.x.saturating_add(overlap.size.w);
            let right_w = rect.loc.x.saturating_add(rect.size.w).saturating_sub(right_x);
            emit(Rectangle::new((right_x, overlap.loc.y).into(), (right_w, overlap.size.h).into()));

            let bottom_y = overlap.loc.y.saturating_add(overlap.size.h);
            let bottom_h = rect.loc.y.saturating_add(rect.size.h).saturating_sub(bottom_y);
            emit(Rectangle::new((rect.loc.x, bottom_y).into(), (rect.size.w, bottom_h).into()));
        }

        self.rotate_left(len);
        self.truncate(self.len() - len);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_matrix_valid() {
        let m: Matrix3x3<i32> = "1, 0, 0; 0, 1, 0; 0, 0, 1".parse().unwrap();
        assert_eq!(m.data, [1, 0, 0, 0, 1, 0, 0, 0, 1]);
    }

    #[test]
    fn parse_matrix_invalid_delimiter() {
        assert!(Matrix3x3::<i32>::from_str("1, 0, 0, 0, 1, 0, 0, 0, 1").is_err());
    }

    #[test]
    fn parse_matrix_wrong_format() {
        assert!(Matrix3x3::<i32>::from_str("1; 0; 0; 0; 1; 0; 0; 0; 1;").is_err());
    }

    #[test]
    fn matrix_multiply() {
        let m: Matrix3x3<f32> = "1, 0, 0; 0, 1, 0; 0, 0, 1".parse().unwrap();
        let v = Vector3D::new(1.0, 2.0, 3.0);
        let result = &m * v;
        assert_eq!(result, v);
    }

    #[test]
    fn subtract_rect_no_overlap() {
        let mut rects = vec![Rectangle::<i32, ()>::from_size((10, 10).into())];
        rects.subtract_rect(Rectangle::new((20, 20).into(), (5, 5).into()));
        assert_eq!(rects.len(), 1);
    }

    #[test]
    fn subtract_rect_full_overlap() {
        let mut rects = vec![Rectangle::<i32, ()>::new((5, 5).into(), (5, 5).into())];
        rects.subtract_rect(Rectangle::new((0, 0).into(), (20, 20).into()));
        assert!(rects.is_empty());
    }
}
