{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wall #-}

module NumHask.Rect
  ( Rect(..)
  , rect
  , corners
  , midRect
  , elementRect
  , singletonRect
  , singularRect
  , intersectionRect
  , containsRect
  , rangeR2
  , rangeR2s
  , projectR2
  , projectRect
  , gridP
  , grid
  ) where

import NumHask.Range
import NumHask.Prelude
import Control.Lens hiding (Magma, singular, element, contains)
import Linear.V2
import Linear.V4

-- | a two-dimensional plane, bounded by ranges.
newtype Rect a = Rect {xy :: V2 (Range a)}
    deriving (Show, Eq, Ord, Functor)

-- | an alternative specification; as a 4-dim vector `V4 x y z w` where:
-- - (x,y) is the lower left corner of a rectangle, and
-- - (z,w) is the upper right corner of a rectangle
rect :: Iso' (V4 a) (Rect a)
rect = iso toRect toV4
    toRect (V4 x y z w) = Rect $ V2 (Range (x,z)) (Range (y,w))
    toV4 (Rect (V2 (Range (x,z)) (Range (y,w)))) = V4 x y z w

-- | a convex hull approach
instance (Ord a) => AdditiveMagma (Rect a) where
    plus (Rect (V2 ax ay)) (Rect (V2 bx yb)) =
        Rect (V2 (ax `plus` bx) (ay `plus` yb))

instance (Ord a, BoundedField a) => AdditiveUnital (Rect a) where
    zero = Rect $ V2 zero zero

instance (Ord a) => AdditiveAssociative (Rect a)
instance (Ord a) => AdditiveCommutative (Rect a)
instance (Ord a, BoundedField a) => Additive (Rect a)

instance (Ord a) => Semigroup (Rect a) where
    (<>) = plus

instance (AdditiveUnital (Rect a), Semigroup (Rect a)) => Monoid (Rect a) where
    mempty = zero
    mappend = (<>)

instance (Ord a) => AdditiveInvertible (Rect a) where
    negate (Rect (V2 x y)) = Rect (V2 (negate x) (negate y))

instance (BoundedField a, Ord a) => AdditiveGroup (Rect a)

-- | natural interpretation of an `a` as an `Rect a`
instance (Ord a) =>
    AdditiveHomomorphic (V2 a) (Rect a) where
    plushom v = singletonRect v

instance (BoundedField a) => MultiplicativeMagma (Rect a) where
    (Rect (V2 a0 b0)) `times` (Rect (V2 a1 b1)) =
        Rect (V2 (a0 `times` a1) (b0 `times` b1))

instance (BoundedField a) => MultiplicativeUnital (Rect a) where
    one = Rect (V2 one one)
instance (BoundedField a) => MultiplicativeAssociative (Rect a)
instance (Ord a, BoundedField a) => MultiplicativeInvertible (Rect a) where
    recip (Rect (V2 a b)) = Rect (V2 (recip a) (recip b))
instance (Ord a, BoundedField a) => MultiplicativeLeftCancellative (Rect a)
instance (Ord a, BoundedField a) => MultiplicativeRightCancellative (Rect a)

instance (BoundedField a, Ord a) => Signed (Rect a) where
    sign (Rect (V2 a b)) = Rect (V2 (sign a) (sign b))
    abs (Rect (V2 a b)) = Rect (V2 (abs a) (abs b))

instance (AdditiveGroup a) => Normed (Rect a) (V2 a) where
    size (Rect (V2 x y)) = V2 (size x) (size y)

instance (Ord a, AdditiveGroup a) => Metric (Rect a) (V2 a) where
    distance (Rect (V2 x y)) (Rect (V2 x1 y1)) = V2 (distance x x1) (distance y y1)

midRect :: (BoundedField a) => Rect a -> V2 a
midRect (Rect (V2 x y)) = V2 (plushom x) (plushom y)

-- | determine whether a point is within the range
elementRect :: (Ord a) => V2 a -> Rect a -> Bool
elementRect (V2 x y) (Rect (V2 rx ry)) = NumHask.Range.element x rx && NumHask.Range.element y ry

-- | is the range a singleton V2 (has zero area)
singularRect :: (Eq a) => Rect a -> Bool
singularRect (Rect (V2 x y)) = NumHask.Range.singular x || NumHask.Range.singular y

singletonRect :: V2 a -> Rect a
singletonRect (V2 x y) = Rect (V2 (singleton x) (singleton y)) 

intersectionRect :: (Ord a) => Rect a -> Rect a -> Rect a
intersectionRect (Rect (V2 x y)) (Rect (V2 x1 y1)) =
    Rect (V2 (NumHask.Range.intersection x x1) (NumHask.Range.intersection y y1))

containsRect :: (Ord a) => Rect a -> Rect a -> Bool
containsRect (Rect (V2 x y)) (Rect (V2 x1 y1)) =
    NumHask.Range.contains x x1 && NumHask.Range.contains y y1

corners :: Rect a -> [V2 a]
corners (Rect (V2 (Range (lx,ux)) (Range (ly,uy)))) = [V2 lx ly, V2 ux uy]

-- | the range Rect of a container of R2s
rangeR2 :: (Traversable f, Ord a, BoundedField a, R2 r) => f (r a) -> Rect a
rangeR2 f = Rect (V2 (range $ view _x <$> f) (range $ view _y <$> f))

-- | range specialized to double traversables
rangeR2s :: (BoundedField a, Traversable g, Traversable f, R2 r, Ord a) =>
    g (f (r a)) -> Rect a
rangeR2s f = foldMap rangeR2 f

-- | project a container of r2 points from an old Rect to a new one
projectR2 :: (R2 r, Field a, Functor f) =>
    Rect a -> Rect a -> f (r a) -> f (r a)
projectR2 (Rect (V2 rx ry)) (Rect (V2 rx' ry')) qs =
    (over _x (project rx rx') . over _y (project ry ry')) <$> qs

-- | project a Rect from an old Rect range to a new one
projectRect :: (Field a) =>
    Rect a -> Rect a -> Rect a -> Rect a
projectRect (Rect (V2 rx ry)) (Rect (V2 rx' ry')) (Rect (V2 rx0 ry0)) =
    Rect (V2 (project rx rx' <$> rx0) (project ry ry' <$> ry0))

-- | grid points on a rectange, divided up by a V2 Int
gridP :: (Field a, FromInteger a) => LinearPos -> Rect a -> V2 Int -> [V2 a]
gridP tp (Rect (V2 rX rY)) (V2 stepX stepY) =
    [V2 x y | x <- linearSpace tp rX stepX, y <- linearSpace tp rY stepY]

-- | a rectangle divided up by a V2 Int, making a list of smaller rectangles
grid :: (BoundedField a, FromInteger a) => Rect a -> V2 Int -> [Rect a]
grid (Rect (V2 rX rY)) (V2 stepX stepY) =
    [ Rect (V2 (Range (x,x+sx)) (Range (y,y+sy)))
    | x <- linearSpace LowerPos rX stepX
    , y <- linearSpace LowerPos rY stepY
    sx = view width rX / fromIntegral stepX
    sy = view width rY / fromIntegral stepY