{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
-- | Module implementing types used for geometry

-- bound calculations.

module Graphics.Rasterific.PlaneBoundable ( PlaneBound( .. )
                                          , PlaneBoundable( .. )
                                          , boundWidth
                                          , boundHeight
                                          , boundLowerLeftCorner
                                          ) where

import Data.Semigroup( Semigroup( .. ) )

import Graphics.Rasterific.Linear( V2( .. ) )
import Graphics.Rasterific.Types
import Graphics.Rasterific.CubicBezier

-- | Represent the minimal axis aligned rectangle

-- in which some primitives can be drawn. Should

-- fit to bezier curve and not use directly their

-- control points.

data PlaneBound = PlaneBound
    { -- | Corner upper left of the bounding box of

      -- the considered primitives.

      _planeMinBound :: !Point
      -- | Corner lower right of the bounding box of

      -- the considered primitives.

    , _planeMaxBound :: !Point
    }
    deriving (Eq, Show)

-- | Extract the width of the bounds

boundWidth :: PlaneBound -> Float
boundWidth (PlaneBound (V2 x0 _) (V2 x1 _)) = x1 - x0

-- | Extract the height of the bound

boundHeight :: PlaneBound -> Float
boundHeight (PlaneBound (V2 _ y0) (V2 _ y1)) = y1 - y0

-- | Extract the position of the lower left corner of the

-- bounds.

boundLowerLeftCorner :: PlaneBound -> Point
boundLowerLeftCorner (PlaneBound (V2 x _) (V2 _ y)) = V2 x y

instance Semigroup PlaneBound where
  (<>) (PlaneBound mini1 maxi1) (PlaneBound mini2 maxi2) =
    PlaneBound (min <$> mini1 <*> mini2)
               (max <$> maxi1 <*> maxi2)

instance Monoid PlaneBound where
  mappend = (<>)
  mempty = PlaneBound infPoint negInfPoint
    where
      infPoint = V2 (1 / 0) (1 / 0)
      negInfPoint = V2 (negate 1 / 0) (negate 1 / 0)

-- | Class used to calculate bounds of various geometrical

-- primitives. The calculated is precise, the bounding should

-- be minimal with respect with drawn curve.

class PlaneBoundable a where
    -- | Given a graphical elements, calculate it's bounds.

    planeBounds :: a -> PlaneBound

instance PlaneBoundable Point where
    planeBounds a = PlaneBound a a

instance PlaneBoundable Line where
    planeBounds (Line p1 p2) = planeBounds p1 <> planeBounds p2

instance PlaneBoundable Bezier where
    planeBounds (Bezier p0 p1 p2) =
        planeBounds (CubicBezier p0 p1 p1 p2)

instance PlaneBoundable CubicBezier where
    planeBounds = foldMap planeBounds . cubicBezierBounds

instance PlaneBoundable Primitive where
    planeBounds (LinePrim l) = planeBounds l
    planeBounds (BezierPrim b) = planeBounds b
    planeBounds (CubicBezierPrim c) = planeBounds c