//! Geometric primitives.

#[allow(unused)]
use crate::F32Ext;

use core::borrow::Borrow;
use core::ops::{Add, Div, Mul, Sub};

/// Represents an angle in degrees or radians.
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Angle(f32);

impl Angle {
    /// Angle of zero degrees.
    pub const ZERO: Self = Self(0.);

    /// Creates a new angle from degrees.
    pub fn from_degrees(degrees: f32) -> Self {
        Self(degrees * core::f32::consts::PI / 180.)
    }

    /// Creates a new angle from radians.
    pub fn from_radians(radians: f32) -> Self {
        Self(radians)
    }

    /// Creates a new angle from gradians.
    pub fn from_gradians(gradians: f32) -> Self {
        Self::from_degrees(gradians / 400. * 360.)
    }

    /// Creates a new angle from turns.
    pub fn from_turns(turns: f32) -> Self {
        Self::from_degrees(turns * 360.)
    }

    /// Returns the angle in radians.
    pub fn to_radians(self) -> f32 {
        self.0
    }

    /// Returns the angle in degrees.
    pub fn to_degrees(self) -> f32 {
        self.0 * 180. / core::f32::consts::PI
    }
}

/// Two dimensional vector.
#[derive(Copy, Clone, PartialEq, Default, Debug)]
pub struct Vector {
    pub x: f32,
    pub y: f32,
}

impl Vector {
    /// Vector with both components set to zero.
    pub const ZERO: Self = Self { x: 0., y: 0. };

    /// Creates a new vector with the specified coordinates.
    #[inline]
    pub const fn new(x: f32, y: f32) -> Self {
        Self { x, y }
    }

    /// Returns the length of the vector.
    #[inline]
    pub fn length(self) -> f32 {
        (self.x * self.x + self.y * self.y).sqrt()
    }

    /// Returns the squared length of the vector.
    #[inline]
    pub fn length_squared(self) -> f32 {
        self.x * self.x + self.y * self.y
    }

    /// Returns the distance between two points.
    #[inline]
    pub fn distance_to(self, other: Self) -> f32 {
        (self - other).length()
    }

    /// Computes the dot product of two vectors.
    #[inline]
    pub fn dot(self, other: Self) -> f32 {
        self.x * other.x + self.y * other.y
    }

    /// Computes the cross product of two vectors.
    #[inline]
    pub fn cross(self, other: Self) -> f32 {
        self.x * other.y - self.y * other.x
    }

    /// Returns a normalized copy of the vector.
    #[inline]
    pub fn normalize(self) -> Self {
        let length = self.length();
        if length == 0. {
            return Self::new(0., 0.);
        }
        let inverse = 1. / length;
        Self::new(self.x * inverse, self.y * inverse)
    }

    /// Returns a new vector containing the smallest integer values greater than
    /// or equal to each component.
    pub fn ceil(self) -> Self {
        Self::new(self.x.ceil(), self.y.ceil())
    }

    /// Returns a new vector containing the largest integer values less than
    /// or equal to each component.
    pub fn floor(self) -> Self {
        Self::new(self.x.floor(), self.y.floor())
    }

    /// Returns the angle to the specified vector.
    pub fn angle_to(self, other: Self) -> Angle {
        Angle::from_radians(self.cross(other).atan2(self.dot(other)))
    }

    /// Returns true if this vector is approximately equal to other using a
    /// standard single precision epsilon value.
    #[inline]
    pub fn nearly_eq(self, other: Vector) -> bool {
        self.nearly_eq_by(other, f32::EPSILON)
    }

    /// Returns true if this vector is approximately equal to other using
    /// the specified epsilon value.
    #[inline]
    pub fn nearly_eq_by(self, other: Vector, epsilon: f32) -> bool {
        (self.x - other.x).abs() < epsilon && (self.y - other.y).abs() < epsilon
    }
}

impl Add for Vector {
    type Output = Self;
    #[inline]
    fn add(self, rhs: Self) -> Self {
        Self::new(self.x + rhs.x, self.y + rhs.y)
    }
}

impl Sub for Vector {
    type Output = Self;
    #[inline]
    fn sub(self, rhs: Self) -> Self {
        Self::new(self.x - rhs.x, self.y - rhs.y)
    }
}

impl Mul for Vector {
    type Output = Self;
    #[inline]
    fn mul(self, rhs: Self) -> Self {
        Self::new(self.x * rhs.x, self.y * rhs.y)
    }
}

impl Mul<f32> for Vector {
    type Output = Self;
    #[inline]
    fn mul(self, rhs: f32) -> Self {
        Self::new(self.x * rhs, self.y * rhs)
    }
}

impl Div for Vector {
    type Output = Self;
    #[inline]
    fn div(self, rhs: Self) -> Self {
        Self::new(self.x / rhs.x, self.y / rhs.y)
    }
}

impl Div<f32> for Vector {
    type Output = Self;
    #[inline]
    fn div(self, rhs: f32) -> Self {
        let s = 1. / rhs;
        Self::new(self.x * s, self.y * s)
    }
}

impl From<[f32; 2]> for Vector {
    fn from(v: [f32; 2]) -> Self {
        Self::new(v[0], v[1])
    }
}

impl From<[i32; 2]> for Vector {
    fn from(v: [i32; 2]) -> Self {
        Self::new(v[0] as f32, v[1] as f32)
    }
}

impl From<(f32, f32)> for Vector {
    fn from(v: (f32, f32)) -> Self {
        Self::new(v.0, v.1)
    }
}

impl From<(i32, i32)> for Vector {
    fn from(v: (i32, i32)) -> Self {
        Self::new(v.0 as f32, v.1 as f32)
    }
}

impl From<(f32, i32)> for Vector {
    fn from(v: (f32, i32)) -> Self {
        Self::new(v.0, v.1 as f32)
    }
}

impl From<(i32, f32)> for Vector {
    fn from(v: (i32, f32)) -> Self {
        Self::new(v.0 as f32, v.1)
    }
}

impl From<f32> for Vector {
    fn from(x: f32) -> Self {
        Self::new(x, x)
    }
}

impl From<i32> for Vector {
    fn from(x: i32) -> Self {
        let x = x as f32;
        Self::new(x, x)
    }
}

impl From<Vector> for [f32; 2] {
    fn from(v: Vector) -> Self {
        [v.x, v.y]
    }
}

impl From<Vector> for (f32, f32) {
    fn from(v: Vector) -> Self {
        (v.x, v.y)
    }
}

/// Alias for vector to distinguish intended use.
pub type Point = Vector;

#[inline(always)]
pub(super) fn normal(start: Vector, end: Vector) -> Vector {
    Vector::new(end.y - start.y, -(end.x - start.x)).normalize()
}

/// Two dimensional transformation matrix.
#[derive(Copy, Clone, Default, Debug)]
pub struct Transform {
    pub xx: f32,
    pub xy: f32,
    pub yx: f32,
    pub yy: f32,
    pub x: f32,
    pub y: f32,
}

impl Transform {
    /// Identity matrix.
    pub const IDENTITY: Self = Self {
        xx: 1.,
        xy: 0.,
        yy: 1.,
        yx: 0.,
        x: 0.,
        y: 0.,
    };

    /// Creates a new transform.
    pub fn new(xx: f32, xy: f32, yx: f32, yy: f32, x: f32, y: f32) -> Self {
        Self {
            xx,
            xy,
            yx,
            yy,
            x,
            y,
        }
    }

    /// Creates a translation transform.
    pub fn translation(x: f32, y: f32) -> Self {
        Self::new(1., 0., 0., 1., x, y)
    }

    /// Creates a rotation transform.
    pub fn rotation(angle: Angle) -> Self {
        let (sin, cos) = angle.0.sin_cos();
        Self {
            xx: cos,
            xy: sin,
            yx: -sin,
            yy: cos,
            x: 0.,
            y: 0.,
        }
    }

    /// Creates a rotation transform around a point.
    pub fn rotation_about(point: impl Into<Point>, angle: Angle) -> Self {
        let p = point.into();
        Self::translation(p.x, p.y)
            .then_rotate(angle)
            .then_translate(-p.x, -p.y)
    }

    /// Creates a scale transform.
    pub fn scale(x: f32, y: f32) -> Self {
        Self::new(x, 0., 0., y, 0., 0.)
    }

    /// Creates a skew transform.
    pub fn skew(x: Angle, y: Angle) -> Self {
        Self {
            xx: 1.,
            xy: y.0.tan(),
            yx: x.0.tan(),
            yy: 1.,
            x: 0.,
            y: 0.,
        }
    }

    fn combine(a: &Transform, b: &Transform) -> Self {
        let xx = a.xx * b.xx + a.yx * b.xy;
        let yx = a.xx * b.yx + a.yx * b.yy;
        let xy = a.xy * b.xx + a.yy * b.xy;
        let yy = a.xy * b.yx + a.yy * b.yy;
        let x = a.x * b.xx + a.y * b.xy + b.x;
        let y = a.x * b.yx + a.y * b.yy + b.y;
        Self {
            xx,
            yx,
            xy,
            yy,
            x,
            y,
        }
    }

    /// Returns a new transform that represents the application of this transform
    /// followed by other.
    pub fn then(&self, other: &Transform) -> Self {
        Self::combine(self, other)
    }

    /// Returns a new transform that represents a translation followed by this
    /// transform.
    pub fn pre_translate(&self, x: f32, y: f32) -> Self {
        Self::combine(&Self::translation(x, y), self)
    }

    /// Returns a new transform that represents this transform followed by a
    /// translation.
    pub fn then_translate(&self, x: f32, y: f32) -> Self {
        let mut t = *self;
        t.x += x;
        t.y += y;
        t
    }

    /// Returns a new transform that represents a rotation followed by this
    /// transform.
    pub fn pre_rotate(&self, angle: Angle) -> Self {
        Self::combine(&Self::rotation(angle), self)
    }

    /// Returns a new transform that represents this transform followed by a
    /// rotation.
    pub fn then_rotate(&self, angle: Angle) -> Self {
        Self::combine(self, &Self::rotation(angle))
    }

    /// Returns a new transform that represents a scale followed by this
    /// transform.
    pub fn pre_scale(&self, x: f32, y: f32) -> Self {
        Self::combine(&Self::scale(x, y), self)
    }

    /// Returns a new transform that represents this transform followed by a
    /// scale.    
    pub fn then_scale(&self, x: f32, y: f32) -> Self {
        Self::combine(self, &Self::scale(x, y))
    }

    /// Returns the determinant of the transform.
    pub fn determinant(&self) -> f32 {
        self.xx * self.yy - self.yx * self.xy
    }

    /// Returns the inverse of the transform, if any.
    pub fn invert(&self) -> Option<Transform> {
        let det = self.determinant();
        if !det.is_finite() || det == 0. {
            return None;
        }
        let s = 1. / det;
        let a = self.xx;
        let b = self.xy;
        let c = self.yx;
        let d = self.yy;
        let x = self.x;
        let y = self.y;
        Some(Transform {
            xx: d * s,
            xy: -b * s,
            yx: -c * s,
            yy: a * s,
            x: (b * y - d * x) * s,
            y: (c * x - a * y) * s,
        })
    }

    /// Returns the result of applying this transform to a point.
    #[inline(always)]
    pub fn transform_point(&self, point: Point) -> Point {
        Vector {
            x: (point.x * self.xx + point.y * self.yx) + self.x,
            y: (point.x * self.xy + point.y * self.yy) + self.y,
        }
    }

    /// Returns the result of applying this transform to a vector.
    #[inline(always)]
    pub fn transform_vector(&self, vector: Vector) -> Vector {
        Vector {
            x: (vector.x * self.xx + vector.y * self.yx),
            y: (vector.x * self.xy + vector.y * self.yy),
        }
    }
}

/// The origin of the coordinate system for rendering.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Origin {
    /// Origin (0, 0) at the top left of the image.
    TopLeft,
    /// Origin (0, 0) at the bottom left of the image.
    BottomLeft,
}

impl Default for Origin {
    fn default() -> Self {
        Self::TopLeft
    }
}

/// Describes the offset and dimensions of a rendered mask.
#[derive(Copy, Clone, Debug, Default)]
pub struct Placement {
    /// Horizontal offset with respect to the origin specified when computing
    /// the placement.
    pub left: i32,
    /// Vertical offset with respect to the origin specified when computing
    /// the placement.
    pub top: i32,
    /// Width in pixels.
    pub width: u32,
    /// Height in pixels.
    pub height: u32,
}

impl Placement {
    /// Given an origin, offset and bounding box, computes the resulting offset
    /// and placement for a tightly bounded mask.
    pub fn compute(
        origin: Origin,
        offset: impl Into<Vector>,
        bounds: &Bounds,
    ) -> (Vector, Placement) {
        let offset = offset.into();
        let mut bounds = *bounds;
        bounds.min = (bounds.min + offset).floor();
        bounds.max = (bounds.max + offset).ceil();
        let offset = Vector::new(-bounds.min.x, -bounds.min.y);
        let width = bounds.width() as u32;
        let height = bounds.height() as u32;
        let left = -offset.x as i32;
        let top = if origin == Origin::BottomLeft {
            (-offset.y).floor() + height as f32
        } else {
            -offset.y
        } as i32;
        (
            offset,
            Placement {
                left,
                top,
                width,
                height,
            },
        )
    }
}

/// Axis-aligned bounding box.
#[derive(Copy, Clone, Default, Debug)]
pub struct Bounds {
    pub min: Point,
    pub max: Point,
}

impl Bounds {
    /// Creates a new bounding box from minimum and maximum points.
    pub fn new(min: Point, max: Point) -> Self {
        Self { min, max }
    }

    /// Creates a new bounding box from a sequence of points.
    pub fn from_points<I>(points: I) -> Self
    where
        I: IntoIterator,
        I::Item: Borrow<Point>,
    {
        let mut b = BoundsBuilder::new();
        for p in points {
            b.add(*p.borrow());
        }
        b.build()
    }

    /// Returns true if the bounding box is empty.
    pub fn is_empty(&self) -> bool {
        self.min.x >= self.max.x || self.min.y >= self.max.y
    }

    /// Returns the width of the bounding box.
    pub fn width(&self) -> f32 {
        self.max.x - self.min.x
    }

    /// Returns the height of the bounding box.
    pub fn height(&self) -> f32 {
        self.max.y - self.min.y
    }

    /// Returns true if the box contains the specified point.
    pub fn contains(&self, point: impl Into<Point>) -> bool {
        let p = point.into();
        p.x > self.min.x && p.x < self.max.x && p.y > self.min.y && p.y < self.max.y
    }
}

pub(super) struct BoundsBuilder {
    pub count: usize,
    #[allow(dead_code)]
    pub start: Point,
    pub current: Point,
    pub min: Point,
    pub max: Point,
}

impl BoundsBuilder {
    pub fn new() -> Self {
        Self {
            count: 0,
            start: Point::ZERO,
            current: Point::ZERO,
            min: Point::new(f32::MAX, f32::MAX),
            max: Point::new(f32::MIN, f32::MIN),
        }
    }

    pub fn add(&mut self, p: Point) -> &mut Self {
        let x = p.x;
        let y = p.y;
        if x < self.min.x {
            self.min.x = x;
        }
        if x > self.max.x {
            self.max.x = x;
        }
        if y < self.min.y {
            self.min.y = y;
        }
        if y > self.max.y {
            self.max.y = y;
        }
        self.count += 1;
        self
    }

    pub fn build(&self) -> Bounds {
        if self.count != 0 {
            Bounds {
                min: self.min,
                max: self.max,
            }
        } else {
            Bounds::default()
        }
    }
}
