{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase   #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Potrace.Base
-- Copyright   :  (c) 2015 Christopher Chalmers
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  c.chalmers@me.com
--
-- A mid-level interface to potrace. Includes helpers for making
-- bitmap images. This is useful for other libraries that want to
-- implement their own tools on top of potrace (see
-- "potrace-diagrams").
--
-- In principle this could be in a separate module but there's already
-- three separate potrace modules and the dependencies aren't huge.


module Graphics.Potrace.Base
  (
    -- * Tracing
    Curve (..)
  , Segment (..)
  , P2 (..)
  , trace
  , trace'
  , traceForest
  , traceForest'

    -- * Bitmaps
  , Bitmap (..)
  , generate
  , index

  -- * Parameters
  , Parameters (..)
  , TurnPolicy (..)
  ) where

import           Control.Applicative
import           Data.Bits
import           Data.Default
import           Data.Monoid
import           Data.Tree
import qualified Data.Vector.Storable as V
import           Foreign.C.Types
import           Foreign.Marshal.Alloc
import           Foreign.Ptr
import           Foreign.Storable
import           System.IO.Unsafe (unsafePerformIO)

import           Bindings.Potrace

------------------------------------------------------------------------
-- Bitmap
------------------------------------------------------------------------

-- | Data type to represent a bit packed image. This can be passed to
--   potrace via 'trace'. The constructor is exported by
--   'Graphics.Potrace.Base' but be aware the underlying vector has a
--   host dependant form. You are advised to use 'generate' to create a
--   'Bitmap'.
data Bitmap = Bitmap
  { bitmapWidth  :: Int
  , bitmapHeight :: Int
  , bitmapDy     :: Int
  , bitmapData   :: V.Vector CULong
  }

-- | Given an image and a predicate for whether a pixel is on or off,
--   create a bit-packed image suitable for passing to potrace.
generate :: Int -> Int -> (Int -> Int -> Bool) -> Bitmap
generate w h f = Bitmap w h dy v
  where
  -- The format of the vector potrace needs depends on the size and
  -- endianness of the machine.
  m = sizeOf (0 :: CULong) * 8

  -- Potrace works in blocks of the Word size m. We make each x
  -- (horizontal) line n words wide, the last word only uses r of its
  -- bits.
  (n,r) = w `divMod` m
  dy = if r == 0 then n else n + 1

  -- Number of potential pixels (bits) in each x line (we only use w of them).
  l = dy * m

  -- Each x line is made up of dy words, so we need dy*h words for
  -- the whole picture.
  v = V.generate (dy*h) $ \i ->
    -- The starting point in the image for current block of pixels.
    let (y,x) = (i*m) `divMod` l
        -- Number of pixels we need to fill.
        c | x + m  > w = r
          | otherwise  = m

        -- Loop to fill word with bits. XXX Deal with endianness
        go !k !b
          | k < 0       = b
          | f (x + k) y = go (k - 1) $ setBit b (m - 1 - k)
          | otherwise   = go (k - 1) b

    in  go (c - 1) 0
{-# INLINE generate #-}

-- | Index a pixel in a 'Bitmap'. This is mainly here for debugging
--   purposes.
index :: Bitmap -> Int -> Int -> Bool
index (Bitmap w h dy v) i j = testBit w (m - 1 - r)
  where
    m = sizeOf (0 :: CULong) * 8
    w = v V.! (j * dy + (i `div` m))
    (n,r) = i `divMod` m
{-# INLINE index #-}

------------------------------------------------------------------------
-- Parameters
------------------------------------------------------------------------

-- | Parameters to control the tracing operation of potrace. The default
--   parameters are
--
-- @
-- Parameters
--   { 'turdSize'     = 2
--   , 'turnPolicy'   = 'MinorityTP'
--   , 'alphaMax'     = 1.0
--   , 'optTolerance' = 'Just' 0.2
--   }
-- @
data Parameters = Parameters
  { _turdSize     :: Int            -- ^ See 'Graphics.Potrace.turdSize'.
  , _turnPolicy   :: TurnPolicy     -- ^ See 'Graphics.Potrace.turnPolicy'.
  , _alphaMax     :: Double         -- ^ See 'Graphics.Potrace.alphaMax'.
  , _optTolerance :: (Maybe Double) -- ^ See 'Graphics.Potrace.optTolerance'.
  }

instance Default Parameters where
  def = Parameters
    { _turdSize     = 2
    , _turnPolicy   = MinorityTP
    , _alphaMax     = 1.0
    , _optTolerance = Just 0.2
    }

-- | How to resolve ambiguities during decomposition of bitmaps into
--   paths.
data TurnPolicy
  = BlackTP    -- ^ Prefers to connect black (foreground) components
  | WhiteTP    -- ^ Prefers to connect white (background) components.
  | LeftTP     -- ^ Always take a left turn.
  | RightTP    -- ^ Always take a right turn.
  | MinorityTP -- ^ Prefers to connect the color (black or white) that
               --   occurs least frequently in a local neighborhood of the
               --   current position.
  | MajorityTP -- ^ Prefers to connect the color (black or white) that
               --   occurs most frequently in a local neighborhood of
               --   the current position. (default)
  | RandomTP   -- ^ Choose pseudo-randomly

-- Parameter internals -------------------------------------------------

-- | Allocate new parameters from potrace's default parameters. This
--   should protect against future changes to the options. When no
--   longer needed, these should be deallocated with
--   'p'potrace_param_free'
params :: Parameters -> IO (Ptr C'potrace_param_s)
params (Parameters ts tp am ot) = do
  c_param <- c'potrace_param_default
  poke (p'potrace_param_s'turdsize     c_param) $ fromIntegral ts
  poke (p'potrace_param_s'turnpolicy   c_param) $ turn tp
  poke (p'potrace_param_s'alphamax     c_param) $ CDouble am
  poke (p'potrace_param_s'opticurve    c_param) $ optiOn
  poke (p'potrace_param_s'opttolerance c_param) $ optiVal
  return c_param
  where (optiOn, optiVal) = case ot of
          Just t  -> (1, CDouble t)
          Nothing -> (0, 0)

turn :: TurnPolicy -> CInt
turn = \case
  BlackTP    -> c'POTRACE_TURNPOLICY_BLACK
  WhiteTP    -> c'POTRACE_TURNPOLICY_WHITE
  LeftTP     -> c'POTRACE_TURNPOLICY_LEFT
  RightTP    -> c'POTRACE_TURNPOLICY_RIGHT
  MinorityTP -> c'POTRACE_TURNPOLICY_MINORITY
  MajorityTP -> c'POTRACE_TURNPOLICY_MAJORITY
  RandomTP   -> c'POTRACE_TURNPOLICY_RANDOM

------------------------------------------------------------------------
-- Tracing
------------------------------------------------------------------------

-- | Data type representing a 2D point were the origin is at the bottom
--   left.
data P2 = P2 {-# UNPACK #-} !Double {-# UNPACK #-} !Double
  deriving (Show, Read)

-- | potrace defines a segment as either 'Bezier' or a 'Corner' (in most
--   systems this is equivalent to linear segments).
data Segment
  = Bezier {-# UNPACK #-} !P2 {-# UNPACK #-} !P2 {-# UNPACK #-} !P2
  | Corner {-# UNPACK #-} !P2 {-# UNPACK #-} !P2
  deriving (Show, Read)

-- | A curve is a list of segments. The starting point is provided for
--   convenience but this is just the final point of the last segment.
data Curve = Curve {-# UNPACK #-} !P2 [Segment]

-- | Trace the bitmap image to a list of curves using potrace with 'def'
--   parameters.
trace :: Bitmap -> [Curve]
trace = trace' def

-- | Trace the bitmap image to a list of curves using potrace with given
--   parameters.
trace' :: Parameters -> Bitmap -> [Curve]
trace' p bm = unsafePerformIO $ unsafeWithImage pathList p bm

-- | Trace the bitmap image as a forest of curves using potrace with 'def'
--   parameters. Each child curve is completely contained in it's
--   parent.
traceForest :: Bitmap -> Forest Curve
traceForest = traceForest' def

-- | Trace the bitmap image as a forest of curves using potrace with
--   given parameters parameters. Each child curve is completely
--   contained in it's parent.
traceForest' :: Parameters -> Bitmap -> Forest Curve
traceForest' p bm = unsafePerformIO $ unsafeWithImage pathForest p bm

-- Internals -----------------------------------------------------------

-- | Go though the curve and turn in into a list of segments.
curve :: C'potrace_curve_s -> IO Curve
curve (C'potrace_curve_s n ts ps_) = Curve <$> p0 <*> go 0 where
  ps = castPtr ps_
  -- The last point in the vector is also the starting point
  p0 = peekElemOff ps (3*fromIntegral n - 1)
  go i | i >= fromIntegral n = pure []
  go i = do
    let o = 3*i
    t <- peekElemOff ts i
    s <- if t == c'POTRACE_CORNER
         then Corner <$> peekElemOff ps (o + 1)
                     <*> peekElemOff ps (o + 2)
         else Bezier <$> peekElemOff ps (o + 0)
                     <*> peekElemOff ps (o + 1)
                     <*> peekElemOff ps (o + 2)
    ss <- go (i+1)
    return (s:ss)

-- | Helper for constructing lists from linked lists of pointers. If the
--   ptr is 'nullPtr' return mempty, otherwise pass the pointer value to
--   the function.
onPtr :: (Storable a, Monoid b) => (a -> IO b) -> Ptr a -> IO b
onPtr f ptr
  | ptr == nullPtr = pure mempty
  | otherwise      = peek ptr >>= f
{-# INLINE onPtr #-}

type State = C'potrace_state_s

-- | List of curves in order, suitable for passing to a renderer.
pathList :: State -> IO [Curve]
pathList (C'potrace_state_s _i ptr0 _) = go ptr0 where
  go = onPtr $ \p -> do
    s  <- curve $ c'potrace_path_s'curve p
    ss <- go (c'potrace_path_s'next p)
    pure (s:ss)

-- | Tree structure of paths where each child is enclosed within it's
--   parent.
pathForest :: State -> IO (Forest Curve)
pathForest (C'potrace_state_s _i ptr0 _) = go ptr0 where
  go = onPtr $ \p -> do
    s  <- curve $ c'potrace_path_s'curve p
    ch <- go $ c'potrace_path_s'childlist p
    sb <- go $ c'potrace_path_s'sibling p
    pure (Node s ch : sb)

unsafeWithImage :: (State -> IO r) -> Parameters -> Bitmap -> IO r
unsafeWithImage f ps (Bitmap w h dy v) = V.unsafeWith v $ \p -> do
  c_param <- params ps
  alloca $ \c_bitmap -> do
    poke c_bitmap $ C'potrace_bitmap_s (fromIntegral w) (fromIntegral h) (fromIntegral dy) p
    c_status <- c'potrace_trace c_param c_bitmap
    r        <- peek c_status >>= f

    c'potrace_state_free c_status
    c'potrace_param_free c_param
    return r
------------------------------------------------------------------------
-- Instances
------------------------------------------------------------------------

instance Storable P2 where
  sizeOf _ = 2 * sizeOf (undefined::Double)
  {-# INLINE sizeOf #-}
  alignment _ = alignment (undefined::Double)
  {-# INLINE alignment #-}
  poke ptr (P2 x y) = poke ptr' x >> pokeElemOff ptr' 1 y
    where ptr' = castPtr ptr
  {-# INLINE poke #-}
  peek ptr = P2 <$> peek ptr' <*> peekElemOff ptr' 1
    where ptr' = castPtr ptr
  {-# INLINE peek #-}