content: {-# LANGUAGE BangPatterns #-}
  module Graphics.Rasterific.Rasterize
      ( CoverageSpan( .. )
      , rasterize
      , toOpaqueCoverage
      , clip
      ) where
  
  import Control.Monad.ST( runST )
  import Data.Fixed( mod' )
  import Data.Monoid( Endo( Endo, appEndo ) )
  import Graphics.Rasterific.Types
  import Graphics.Rasterific.QuadraticBezier
  import Graphics.Rasterific.CubicBezier
  import Graphics.Rasterific.Line
  import qualified Data.Vector as V
  import qualified Data.Vector.Algorithms.Intro as VS
  
  data CoverageSpan = CoverageSpan
      { _coverageX      :: {-# UNPACK #-} !Float
      , _coverageY      :: {-# UNPACK #-} !Float
      , _coverageVal    :: {-# UNPACK #-} !Float
      , _coverageLength :: {-# UNPACK #-} !Float
      }
      deriving Show
  
  toOpaqueCoverage :: CoverageSpan -> CoverageSpan
  {-# INLINE toOpaqueCoverage #-}
  toOpaqueCoverage coverage = coverage { _coverageVal = 1 }
  
  combineEdgeSamples :: (Float -> Float) -> V.Vector EdgeSample
                     -> [CoverageSpan]
  {-# INLINE combineEdgeSamples #-}
  combineEdgeSamples prepareCoverage vec = go 0 0 0 0 0
    where
      !maxi = V.length vec
      go !ix !x !y !a !_h | ix >= maxi = [CoverageSpan x y (prepareCoverage a) 1]
      go !ix !x !y !a !h = sub (vec `V.unsafeIndex` ix) where
        sub (EdgeSample x' y' a' h')
          | y == y' && x == x' = go (ix + 1) x' y' (a + a') (h + h')
          | y == y' = p1 : p2 : go (ix + 1) x' y' (h + a') (h + h')
          | otherwise =
             CoverageSpan x y (prepareCoverage a) 1 : go (ix + 1) x' y' a' h'
               where p1 = CoverageSpan x y (prepareCoverage a) 1
                     p2 = CoverageSpan (x + 1) y (prepareCoverage h) (x' - x - 1)
  
  -- | Clip the geometry to a rectangle.
  clip :: Point     -- ^ Minimum point (corner upper left)
       -> Point     -- ^ Maximum point (corner bottom right)
       -> Primitive -- ^ Primitive to be clipped
       -> Container Primitive
  clip mini maxi (LinePrim l) = clipLine mini maxi l
  clip mini maxi (BezierPrim b) = clipBezier mini maxi b
  clip mini maxi (CubicBezierPrim c) = clipCubicBezier mini maxi c
  
  decompose :: Primitive -> Producer EdgeSample
  decompose (LinePrim l) = decomposeLine l
  decompose (BezierPrim b) = decomposeBeziers b
  decompose (CubicBezierPrim c) =
      {-decomposeCubicBezierForwardDifference c-}
      decomposeCubicBeziers c
  
  xyCompare :: EdgeSample -> EdgeSample -> Ordering
  {-# INLINE xyCompare #-}
  xyCompare !(EdgeSample { _sampleY = ay, _sampleX = ax })
            !(EdgeSample { _sampleY = by, _sampleX = bx }) =
    case compare ay by of
      EQ -> compare ax bx
      c -> c
  
  sortEdgeSamples :: [EdgeSample] -> V.Vector EdgeSample
  sortEdgeSamples samples = runST $ do
      -- Resist the urge to make this a storable vector,
      -- it is actually a pessimisation.
      mutableVector <- V.unsafeThaw $ V.fromList samples
      VS.sortBy xyCompare mutableVector
      V.unsafeFreeze mutableVector
  
  rasterize :: FillMethod -> Container Primitive -> [CoverageSpan]
  rasterize method = 
    case method of
      FillWinding -> combineEdgeSamples combineWinding 
                          . sortEdgeSamples
                          . (($ []) . appEndo)
                          . foldMap (Endo . decompose)
      FillEvenOdd -> combineEdgeSamples combineEvenOdd
                          . sortEdgeSamples
                          . (($ []) . appEndo)
                          . foldMap (Endo . decompose)
    where combineWinding = min 1 . abs
          combineEvenOdd cov = abs $ abs (cov - 1) `mod'` 2 - 1
  
  