{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
-- | Compositor handle the pixel composition, which
-- leads to texture composition.
-- Very much a work in progress
module Graphics.Rasterific.Compositor
    ( Compositor
    , Modulable( .. )
    , InterpolablePixel( .. )
    , maxDistance
    , RenderablePixel
    , ModulablePixel
    , compositionDestination
    , compositionAlpha
    , emptyPx
    ) where

import Foreign.Storable( Storable )
import Data.Bits( unsafeShiftR )
import Data.Word( Word8, Word32 )

import Codec.Picture.Types
    ( Pixel( .. )
    , PixelRGB8( .. )
    , PixelRGBA8( .. )
    , PackeablePixel( .. ) )

import Graphics.Rasterific.Linear
import Graphics.Rasterific.Types

type Compositor px =
    PixelBaseComponent px ->
        PixelBaseComponent px -> px -> px -> px

-- | Used for Coon patch rendering
class ( Applicative (Holder a)
      , Functor  (Holder a)
      , Foldable (Holder a)
      , Additive (Holder a) ) => InterpolablePixel a where
  type Holder a :: * -> *
  toFloatPixel :: a -> Holder a Float
  fromFloatPixel :: Holder a Float -> a
  maxRepresentable :: Proxy a -> Float

maxDistance :: InterpolablePixel a => a -> a -> Float
maxDistance p1 p2 = maximum $ abs <$> (toFloatPixel p1 ^-^ toFloatPixel p2)

instance InterpolablePixel Float where
  type Holder Float = V1
  toFloatPixel = V1
  fromFloatPixel (V1 f) = f
  maxRepresentable Proxy = 1

instance InterpolablePixel Word8 where
  type Holder Word8 = V1
  toFloatPixel = V1 . fromIntegral
  fromFloatPixel (V1 f) = floor f
  maxRepresentable Proxy = 255

instance InterpolablePixel PixelRGB8 where
  type Holder PixelRGB8 = V3
  toFloatPixel (PixelRGB8 r g b) = V3 (to r) (to g) (to b) where to n = fromIntegral n
  fromFloatPixel (V3 r g b) = PixelRGB8 (to r) (to g) (to b) where to = floor
  maxRepresentable Proxy = 255

instance InterpolablePixel PixelRGBA8 where
  type Holder PixelRGBA8 = V4
  toFloatPixel (PixelRGBA8 r g b a) = V4 (to r) (to g) (to b) (to a)
    where to n = fromIntegral n
  fromFloatPixel (V4 r g b a) = PixelRGBA8 (to r) (to g) (to b) (to a)
    where to = floor
  maxRepresentable Proxy = 255

-- | This constraint ensure that a type is a pixel
-- and we're allowed to modulate it's color components
-- generically.
type ModulablePixel px =
    ( Pixel px
    , PackeablePixel px
    , InterpolablePixel px
    , InterpolablePixel (PixelBaseComponent px)
    , Storable (PackedRepresentation px)
    , Modulable (PixelBaseComponent px))

-- | This constraint tells us that pixel component
-- must also be pixel and be the "bottom" of component,
-- we cannot go further than a PixelBaseComponent level.
--
-- Tested pixel types are PixelRGBA8 & Pixel8
type RenderablePixel px =
    ( ModulablePixel px
    , Pixel (PixelBaseComponent px)
    , PackeablePixel (PixelBaseComponent px)
    , Num (PackedRepresentation px)
    , Num (PackedRepresentation (PixelBaseComponent px))
    , Num (Holder px Float)
    , Num (Holder (PixelBaseComponent px) Float)
    , Storable (PackedRepresentation (PixelBaseComponent px))
    , PixelBaseComponent (PixelBaseComponent px)
            ~ (PixelBaseComponent px)
    )

-- | Typeclass intented at pixel value modulation.
-- May be throwed out soon.
class (Ord a, Num a) => Modulable a where
  -- | Empty value representing total transparency for the given type.
  emptyValue :: a
  -- | Full value representing total opacity for a given type.
  fullValue  :: a
  -- | Given a Float in [0; 1], return the coverage in [emptyValue; fullValue]
  -- The second value is the inverse coverage
  clampCoverage :: Float -> (a, a)

  -- | Modulate two elements, staying in the [emptyValue; fullValue] range.
  modulate :: a -> a -> a

  -- | Implement a division between two elements.
  modiv :: a -> a -> a

  alphaOver :: a -- ^ coverage
            -> a -- ^ inverse coverage
            -> a -- ^ background
            -> a -- ^ foreground
            -> a
  alphaCompose :: a -> a -> a -> a -> a

  -- | Like modulate but also return the inverse coverage.
  coverageModulate :: a -> a -> (a, a)
  {-# INLINE coverageModulate #-}
  coverageModulate c a = (clamped, fullValue - clamped)
    where clamped = modulate a c

instance Modulable Float where
  emptyValue = 0
  fullValue = 1
  clampCoverage f = (f, 1 - f)
  modulate = (*)
  modiv = (/)
  alphaCompose coverage inverseCoverage backAlpha _ =
      coverage + backAlpha * inverseCoverage
  alphaOver coverage inverseCoverage background painted =
      coverage * painted + background * inverseCoverage

div255 :: Word32 -> Word32
{-# INLINE div255 #-}
div255 v = (v + (v `unsafeShiftR` 8)) `unsafeShiftR` 8

instance Modulable Word8 where
  {-# INLINE emptyValue #-}
  emptyValue = 0
  {-# INLINE fullValue #-}
  fullValue = 255
  {-# INLINE clampCoverage #-}
  clampCoverage f = (fromIntegral c, fromIntegral $ 255 - c)
     where c = toWord8 f

  {-# INLINE modulate #-}
  modulate c a = fromIntegral . div255 $ fi c * fi a + 128
    where fi :: Word8 -> Word32
          fi = fromIntegral

  {-# INLINE modiv #-}
  modiv c 0 = c
  modiv c a = fromIntegral . min 255 $ (fi c * 255) `div` fi a
    where fi :: Word8 -> Word32
          fi = fromIntegral

  {-# INLINE alphaCompose #-}
  alphaCompose coverage inverseCoverage backgroundAlpha _ =
      fromIntegral $ div255 v
        where fi :: Word8 -> Word32
              fi = fromIntegral
              v = fi coverage * 255
                + fi backgroundAlpha * fi inverseCoverage + 128

  {-# INLINE alphaOver #-}
  alphaOver coverage inverseCoverage background painted =
      fromIntegral $ div255 v
    where fi :: Word8 -> Word32
          fi = fromIntegral
          v = fi coverage * fi painted + fi background * fi inverseCoverage + 128


toWord8 :: Float -> Int
{-# INLINE toWord8 #-}
toWord8 r = floor $ r * 255 + 0.5

compositionDestination :: (Pixel px, Modulable (PixelBaseComponent px))
                       => Compositor px
compositionDestination c _ _ = colorMap (modulate c)

compositionAlpha :: (Pixel px, Modulable (PixelBaseComponent px))
                 => Compositor px
{-# INLINE compositionAlpha #-}
compositionAlpha c ic
    | c == emptyValue = const
    | c == fullValue = \_ n -> n
    | otherwise = \bottom top ->
        let bottomOpacity = pixelOpacity bottom
            alphaOut = alphaCompose c ic bottomOpacity (pixelOpacity top)
            colorComposer _ back fore =
                alphaOver c ic (back `modulate` bottomOpacity) fore
                    `modiv` alphaOut
        in
        mixWithAlpha colorComposer (\_ _ -> alphaOut) bottom top

emptyPx :: (RenderablePixel px) => px
-- | Really need a "builder" function for pixel
emptyPx = colorMap (const emptyValue) $ unpackPixel 0