```module Point
( Point
, pattern Point
, xVal
, yVal
, fromFloat
, pointMap
, pointAsTuple
-- * Operators for doing arithmetic on a point with a scalar float value
, (|+|)
, (|*|)
, (|/|)
-- * Geometric point manipulation functions
, cross
, dot
, angleBetween
, mag
, magSquared
, mirrorP
, rotateP
, cartesianProduct
) where

import ApproxEq

-- | A point in 2-d space:
--
--    @Point 3 4@
data Point = Point !Float !Float deriving (Eq, Show)

instance Num Point where
Point x1 y1 + Point x2 y2 = Point (x1 + x2) (y1 + y2)
Point x1 y1 * Point x2 y2 = Point (x1 * x2) (y1 * y2)
Point x1 y1 - Point x2 y2 = Point (x1 - x2) (y1 - y2)
abs (Point x y) = Point (abs x) (abs y)
signum (Point x y) = Point (signum x) (signum y)
negate (Point x y) = Point (x * (-1)) (y * (-1))
fromInteger i = Point (fromInteger i) (fromInteger i)

instance Fractional Point where
Point x1 y1 / Point x2 y2 = Point (x1 / x2) (y1 / y2)
recip (Point x y) = Point y x
fromRational r = Point (fromRational r) (fromRational r)

instance Floating Point where
pi = Point pi pi
exp (Point x y) = Point (exp x) (exp y)
log (Point x y) = Point (log x) (log y)
sin (Point x y) = Point (sin x) (sin y)
cos (Point x y) = Point (cos x) (cos y)
asin (Point x y) = Point (asin x) (asin y)
acos (Point x y) = Point (acos x) (acos y)
atan (Point x y) = Point (atan x) (atan y)
sinh (Point x y) = Point (sinh x) (sinh y)
cosh (Point x y) = Point (cosh x) (cosh y)
asinh (Point x y) = Point (asinh x) (asinh y)
acosh (Point x y) = Point (acosh x) (acosh y)
atanh (Point x y) = Point (atanh x) (atanh y)

instance ApproxEq Point where
approxEqual a b epsilon = let Point dx dy = abs (a - b) in
dx < epsilon && dy < epsilon

instance Ord Point where
compare a b = pointAsTuple a `compare` pointAsTuple b
(<=) a b = pointAsTuple a <= pointAsTuple b

-- | Extract the x axis value from a point
xVal :: Point -> Float
xVal (Point coord _) = coord

-- | Extract the y axis value from a point
yVal :: Point -> Float
yVal (Point _ coord) = coord

-- | Construct a point from a scalar value.
fromFloat :: Float -> Point
fromFloat f = Point f f

-- | Map a float -> float function over a point.
pointMap :: (Float -> Float) -> Point -> Point
pointMap f (Point x y) = Point (f x) (f y)

-- | Convert a point to a 2-tuple representation.
pointAsTuple :: Point -> (Float, Float)
pointAsTuple (Point x y) = (x, y)

-- | Add a scalar value to a point:
--
-- >>> Point 1 2 |+| 5
-- Point 6 7
(|+|) :: Point -> Float -> Point
(|+|) p v = p + fromFloat v

-- | Multiply a point by a scalar value:
--
-- >>> Point 1 2 |*| 5
-- Point 5 10
(|*|) :: Point -> Float -> Point
(|*|) p v = p * fromFloat v

-- | Divide a point by a scalar value:
--
-- >>> Point 10 10 |/| 5
-- Point 2 2
(|/|) :: Point -> Float -> Point
(|/|) p v = p / fromFloat v

cross :: Point -> Point -> Float
cross (Point x1 y1) (Point x2 y2) = x1 * y2 - y1 * x2

dot :: Point -> Point -> Float
dot (Point x1 y1) (Point x2 y2) = x1 * x2 + y1 * y2

angleBetween :: Point -> Point -> Float
angleBetween p1 p2 = atan2 (cross p1 p2) (dot p1 p2)

mag :: Point -> Float
mag p = sqrt (dot p p)

magSquared :: Point -> Float
magSquared p = dot p p

-- | Mirror point a about a line through point p along vector v
mirrorP :: Point -> Point -> Point -> Point
mirrorP a p v = (-a) + 2 * p + 2 * w |*| dot (a - p) w
where w = v |/| mag v

-- | Rotate point a about a line through point p along vector t
rotateP :: Point -> Point -> Float -> Point
rotateP a p t = p + Point (ax * cos t - ay * sin t) (ax * sin t + ay * cos t)
where
Point ax ay = a - p

-- | Return the cartesian product of a set of coordinates on the X axis
-- and a set of coordinates on the Y axis as a series of points.
cartesianProduct :: [Float] -> [Float] -> [Point]
cartesianProduct xs ys = Point <\$> xs <*> ys

```