use std::{fmt, ops};
use zng_var::{animation::Transitionable, impl_from_and_into_var};
use super::{Factor2d, LayoutMask, Length, Point, Px, PxPoint, PxRect};
#[derive(Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
pub struct Line {
    pub start: Point,
    pub end: Point,
}
impl fmt::Debug for Line {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            f.debug_struct("Line").field("start", &self.start).field("end", &self.end).finish()
        } else {
            write!(f, "{:?}.to{:?}", self.start, self.end)
        }
    }
}
impl fmt::Display for Line {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(p) = f.precision() {
            write!(f, "{:.p$} to {:.p$}", self.start, self.end, p = p)
        } else {
            write!(f, "{} to {}", self.start, self.end)
        }
    }
}
impl Line {
    pub fn new<S: Into<Point>, E: Into<Point>>(start: S, end: E) -> Self {
        Line {
            start: start.into(),
            end: end.into(),
        }
    }
    pub fn zero() -> Line {
        Line {
            start: Point::zero(),
            end: Point::zero(),
        }
    }
    pub fn to_top() -> Line {
        Line {
            start: Point::bottom(),
            end: Point::top(),
        }
    }
    pub fn to_bottom() -> Line {
        Line {
            start: Point::top(),
            end: Point::bottom(),
        }
    }
    pub fn to_right() -> Line {
        Line {
            start: Point::left(),
            end: Point::right(),
        }
    }
    pub fn to_left() -> Line {
        Line {
            start: Point::right(),
            end: Point::left(),
        }
    }
    pub fn to_top_left() -> Line {
        Line {
            start: Point::bottom_right(),
            end: Point::top_left(),
        }
    }
    pub fn to_top_right() -> Line {
        Line {
            start: Point::bottom_left(),
            end: Point::top_right(),
        }
    }
    pub fn to_bottom_left() -> Line {
        Line {
            start: Point::top_right(),
            end: Point::bottom_left(),
        }
    }
    pub fn to_bottom_right() -> Line {
        Line {
            start: Point::top_left(),
            end: Point::bottom_right(),
        }
    }
}
impl super::Layout2d for Line {
    type Px = PxLine;
    fn layout_dft(&self, default: Self::Px) -> Self::Px {
        PxLine {
            start: self.start.layout_dft(default.start),
            end: self.end.layout_dft(default.end),
        }
    }
    fn affect_mask(&self) -> LayoutMask {
        self.start.affect_mask() | self.end.affect_mask()
    }
}
impl_from_and_into_var! {
    fn from(line: PxLine) -> Line {
        Line::new(line.start, line.end)
    }
}
#[derive(Clone, Default, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PxLine {
    pub start: PxPoint,
    pub end: PxPoint,
}
impl PxLine {
    pub fn new(start: PxPoint, end: PxPoint) -> Self {
        Self { start, end }
    }
    pub fn zero() -> Self {
        Self::new(PxPoint::zero(), PxPoint::zero())
    }
    pub fn length(self) -> Px {
        let s = self.start.cast::<f32>();
        let e = self.end.cast::<f32>();
        Px(s.distance_to(e).round() as i32)
    }
    pub fn bounds(self) -> PxRect {
        PxRect::from_points([self.start, self.end])
    }
    pub fn normalize(self) -> PxLine {
        let start = self.start.min(self.end);
        let end = self.start.max(self.end);
        PxLine { start, end }
    }
}
pub trait LineFromTuplesBuilder {
    fn to<X2: Into<Length>, Y2: Into<Length>>(self, x2: X2, y2: Y2) -> Line;
}
impl<X1: Into<Length>, Y1: Into<Length>> LineFromTuplesBuilder for (X1, Y1) {
    fn to<X2: Into<Length>, Y2: Into<Length>>(self, x2: X2, y2: Y2) -> Line {
        Line::new(self, (x2, y2))
    }
}
impl<S: Into<Factor2d>> ops::Mul<S> for Line {
    type Output = Self;
    fn mul(mut self, rhs: S) -> Self {
        self *= rhs;
        self
    }
}
impl<'a, S: Into<Factor2d>> ops::Mul<S> for &'a Line {
    type Output = Line;
    fn mul(self, rhs: S) -> Self::Output {
        self.clone() * rhs
    }
}
impl<S: Into<Factor2d>> ops::MulAssign<S> for Line {
    fn mul_assign(&mut self, rhs: S) {
        let rhs = rhs.into();
        self.start *= rhs;
        self.end *= rhs;
    }
}
impl<S: Into<Factor2d>> ops::Div<S> for Line {
    type Output = Self;
    fn div(mut self, rhs: S) -> Self {
        self /= rhs;
        self
    }
}
impl<'a, S: Into<Factor2d>> ops::Div<S> for &'a Line {
    type Output = Line;
    fn div(self, rhs: S) -> Self::Output {
        self.clone() / rhs
    }
}
impl<S: Into<Factor2d>> ops::DivAssign<S> for Line {
    fn div_assign(&mut self, rhs: S) {
        let rhs = rhs.into();
        self.start /= rhs;
        self.end /= rhs;
    }
}
impl ops::Add for Line {
    type Output = Self;
    fn add(mut self, rhs: Self) -> Self {
        self += rhs;
        self
    }
}
impl ops::AddAssign for Line {
    fn add_assign(&mut self, rhs: Self) {
        self.start += rhs.start;
        self.end += rhs.end;
    }
}
impl ops::Sub for Line {
    type Output = Self;
    fn sub(mut self, rhs: Self) -> Self {
        self -= rhs;
        self
    }
}
impl ops::SubAssign for Line {
    fn sub_assign(&mut self, rhs: Self) {
        self.start -= rhs.start;
        self.end -= rhs.end;
    }
}