{-# LANGUAGE FlexibleContexts #-}
module Graphics.ImageMagick.MagickWand.MagickWand
  ( withMagickWandGenesis
  , localGenesis
  -- * Creation
  , magickWand
  , wandResource
--  , magickWandFromImage
--  , isMagickWand
  , cloneMagickWand
--  , clearMagickWand
  , getSize
  , setSize
  , setImageArtifact
  , deleteImageArtifact
  , getIteratorIndex
  , setIteratorIndex
--  , setFirstIterator
--  , setLastIterator
  , resetIterator
  , magickIterate
  , magickIterateReverse
  , deleteOption
  , getOption
  , setOption
  , getOptions
  , deleteImageProperty
  , getImageProperty
  , setImageProperty
  , getImageProperties
  , getImageProfile
  , removeImageProfile
  , setImageProfile
  , getImageProfiles
--  , queryConfigureOption
--  , queryConfigureOptions
--  , queryFontMetrics
--  , queryMultilineFontMetrics
--  , queryFonts
--  , relinquishMemory
--  , deleteImageArtifact
--    , deleteImageProperty
--    , getAntialias
--    , getBackgroundColor
   , getColorspace
   , getCompression
   , getCompressionQuality
--    , getCopyright
--    , getFilename
--    , getFont
--    , getFormat
--    , getGravity
--    , getHomeURL
--    , getImageArtifact
  , getImageArtifacts
--    , getInterlaceScheme
--    , getInterpolateMethod
--    , getOrientation
--    , getPackageName
--    , getPage
--    , getPointsize
--    , getQuantumDepth
--    , getQuantumRange
--    , getReleaseDate
    , getImageResolution
--    , getResource
--    , getResourceLimit
--    , getSamplingFactors
--    , getSize
--    , getSizeOffset
--    , getType
--    , getVersion
--    , profileImage
--    , removeImageProfile
--    , setAntialias
--    , setBackgroundColor
  , setColorspace
  , setCompression
  , setCompressionQuality
--    , setDepth
--    , setExtract
--    , setFilename
--    , setFont
--    , setFormat
--    , setGravity
--    , setImageArtifact
--    , setImageProfile
--    , setInterlaceScheme
--    , setInterpolateMethod
--    , setOrientation
--    , setPage
--    , setPassphrase
--    , setPointsize
--    , setProgressMonitor
--    , setResourceLimit
    , setImageResolution
--    , setSamplingFactors
--    , setSizeOffset
--    , setType
  ) where

import           Control.Applicative                                ((<$>))
import           Control.Exception
import           Control.Monad
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Resource
import           Data.ByteString
import           Data.Text                                          (Text)
import           Data.Text.Encoding                                 (decodeUtf8,
                                                                     encodeUtf8)
import           Data.Vector.Storable                               (Vector)
import qualified Data.Vector.Storable                               as V
import           Foreign                                            hiding
                                                                     (void)
import           Foreign.C.String
import           Foreign.C.Types
import qualified Graphics.ImageMagick.MagickWand.FFI.MagickWand     as F
import           Graphics.ImageMagick.MagickWand.FFI.Types
import qualified Graphics.ImageMagick.MagickWand.FFI.WandProperties as F
import           Graphics.ImageMagick.MagickWand.Types
import           Graphics.ImageMagick.MagickWand.Utils

-- | Create magic wand environment and closes it at the
-- end of the work, should wrap all MagickWand functions
-- withMagickWandGenesis :: IO a -> IO a
-- withMagickWandGenesis :: (MonadCatchIO m, MonadBaseControl IO m, MonadCatchIO (ResourceT IO)) => (ResourceT m c) -> m c
withMagickWandGenesis :: ResourceT IO c -> IO c
withMagickWandGenesis f = bracket start finish (\_ -> runResourceT f)
  where
    start = liftIO F.magickWandGenesis
    finish = liftIO . const F.magickWandTerminus

-- | Open a nested block inside genesis (for tracking nested resources)
localGenesis :: MonadBaseControl IO m => ResourceT m a -> m a
localGenesis f = runResourceT f

magickWand :: (MonadResource m) => m (ReleaseKey, Ptr MagickWand)
magickWand = wandResource F.newMagickWand

magickIterateF :: (MonadResource m) =>
                 (PMagickWand -> IO ())
                 -> (PMagickWand -> IO MagickBooleanType)
                 -> PMagickWand -> (PMagickWand -> m ()) -> m ()
magickIterateF initF next w f = liftIO (initF w) >> go -- TODO: use fix
  where
    go = do
      i <- liftIO $ next w
      when (i==mTrue) $ f w >> go

magickIterate :: (MonadResource m) => Ptr MagickWand -> (Ptr MagickWand -> m ()) -> m ()
magickIterate = magickIterateF F.magickResetIterator F.magickNextImage

magickIterateReverse :: (MonadResource m) => Ptr MagickWand -> (Ptr MagickWand -> m ()) -> m ()
magickIterateReverse = magickIterateF F.magickSetLastIterator F.magickPreviousImage

wandResource :: (MonadResource m) => (IO (Ptr MagickWand)) -> m (ReleaseKey, Ptr MagickWand)
wandResource f = allocate f destroy
  where destroy = void . F.destroyMagickWand

cloneMagickWand :: (MonadResource m) => Ptr MagickWand -> m (ReleaseKey, Ptr MagickWand)
cloneMagickWand = wandResource . F.cloneMagickWand

setSize :: (MonadResource m) => Ptr MagickWand -> Int -> Int -> m ()
setSize w cols rows = withException_ w $ F.magickSetSize w (fromIntegral cols) (fromIntegral rows)

-- | Returns the size associated with the magick wand.
getSize :: (MonadResource m) => Ptr MagickWand -> m (Int, Int)
getSize w = liftIO $ alloca $ \pw -> do
  height <- alloca $ \ph -> F.magickGetSize w pw ph >> peek ph >>= return
  width <- peek pw
  return (fromIntegral width, fromIntegral height)


-- | MagickSetImageArtifact() associates a artifact with an image.
-- The format of the MagickSetImageArtifact method is:
setImageArtifact :: (MonadResource m) => PMagickWand -> ByteString -> ByteString -> m () -- TODO use normal types
setImageArtifact w a v = withException_ w $ useAsCString a
                                          $ \a' -> useAsCString v
                                          $ F.magickSetImageArtifact w a'

-- | MagickDeleteImageArtifact() deletes a wand artifact.
deleteImageArtifact :: (MonadResource m) => PMagickWand -> ByteString -> m ()
deleteImageArtifact w a = withException_ w $ useAsCString a
                                           $ F.magickDeleteImageArtifact w

-- | Sets the iterator to the given position in the image list specified
-- with the index parameter. A zero index will set the first image as
-- current, and so on. Negative indexes can be used to specify an image
-- relative to the end of the images in the wand, with -1 being the last
-- image in the wand.
setIteratorIndex :: (MonadResource m) => Ptr MagickWand -> Int -> m ()
setIteratorIndex w i = withException_ w $ F.magickSetIteratorIndex w (fromIntegral i)

-- | Returns the position of the iterator in the image list.
getIteratorIndex :: (MonadResource m) => Ptr MagickWand -> m Int
getIteratorIndex w = liftIO $ fromIntegral <$> F.magickGetIteratorIndex w

resetIterator :: (MonadResource m) => Ptr MagickWand -> m ()
resetIterator = liftIO . F.magickResetIterator

getOption :: (MonadResource m) => Ptr MagickWand -> Text -> m Text
getOption w key = liftIO $ do
  cstr <- useAsCString (encodeUtf8 key) (F.magickGetOption w)
  value <- decodeUtf8 <$> packCString cstr
  F.magickRelinquishMemory (castPtr cstr)
  return value

-- | Associates one or options with the wand (e.g. setOption wand "jpeg:perserve" "yes").
deleteOption :: (MonadResource m) => Ptr MagickWand -> Text -> m ()
deleteOption w key =
  withException_ w $ useAsCString (encodeUtf8 key) (F.magickDeleteOption w)

-- | Associates one or options with the wand (e.g. setOption wand "jpeg:perserve" "yes").
setOption :: (MonadResource m) => Ptr MagickWand -> Text -> Text -> m ()
setOption w key value =
  withException_ w $ useAsCString (encodeUtf8 key) $
  \cstr -> useAsCString (encodeUtf8 value) (F.magickSetOption w cstr)

getOptions :: (MonadResource m) => Ptr MagickWand -> Text -> m [Text]
getOptions w pattern = liftIO $ alloca $ \pn -> do
  poptionps <- useAsCString (encodeUtf8 pattern) (\cstr -> F.magickGetOptions w cstr pn)
  n <- fromIntegral <$> peek pn
  optionps <- peekArray n poptionps
  options <- forM optionps $ \optionp -> do
    option <- decodeUtf8 <$> packCString optionp
    F.magickRelinquishMemory (castPtr optionp)
    return option
  F.magickRelinquishMemory (castPtr poptionps)
  return options

-- | Deletes a wand property
deleteImageProperty :: (MonadResource m) => Ptr MagickWand -> Text -> m ()
deleteImageProperty w prop =
  withException_ w $ useAsCString (encodeUtf8 prop) (F.magickDeleteImageProperty w)

-- | Returns a value associated with the specified property
getImageProperty :: (MonadResource m) => Ptr MagickWand -> Text -> m Text
getImageProperty w prop = liftIO $ do
  cstr <- useAsCString (encodeUtf8 prop) (F.magickGetImageProperty w)
  value <- decodeUtf8 <$> packCString cstr
  F.magickRelinquishMemory (castPtr cstr)
  return value

-- | Associates a property with an image.
setImageProperty :: (MonadResource m) => Ptr MagickWand -> Text -> Text -> m ()
setImageProperty w prop value =
  withException_ w $ useAsCString (encodeUtf8 prop) $
  \cstr -> useAsCString (encodeUtf8 value) (F.magickSetImageProperty w cstr)

-- | Returns all the property names that match the specified pattern associated
-- with a wand
getImageProperties :: (MonadResource m) => Ptr MagickWand -> Text -> m [Text]
getImageProperties w pattern = liftIO $ alloca $ \pn -> do
  ppropps <- useAsCString (encodeUtf8 pattern) (\cstr -> F.magickGetImageProperties w cstr pn)
  n <- fromIntegral <$> peek pn
  propps <- peekArray n ppropps
  props <- forM propps $ \propp -> do
    prop <- decodeUtf8 <$> packCString propp
    F.magickRelinquishMemory (castPtr propp)
    return prop
  F.magickRelinquishMemory (castPtr ppropps)
  return props

getProfile :: (MonadResource m) =>
              (PMagickWand -> CString ->  Ptr CSize -> IO (Ptr Word8)) ->
              PMagickWand -> Text -> m (Vector Word8)
getProfile f w name = liftIO $ do
  (pprofile, len) <- alloca $ \pn -> useAsCString (encodeUtf8 name) $ \cstr -> do
    p <- f w cstr pn
    n <- fromIntegral <$> peek pn
    return (p,n)
  -- TODO: maybe we should use copyBytes instead?
  profile <- V.generateM len (peekElemOff pprofile)
  F.magickRelinquishMemory (castPtr pprofile)
  return profile

-- | Returns the named image profile.
getImageProfile :: (MonadResource m) => Ptr MagickWand -> Text -> m (Vector Word8)
getImageProfile = getProfile F.magickGetImageProfile

-- | Removes the named image profile and returns it
removeImageProfile :: (MonadResource m) => Ptr MagickWand -> Text -> m (Vector Word8)
removeImageProfile = getProfile F.magickRemoveImageProfile

-- | Adds a named profile to the magick wand. If a profile with the same
-- name already exists, it is replaced. This method differs from the
-- `profileImage` method in that it does not apply any CMS color profiles.
setImageProfile :: (MonadResource m) => Ptr MagickWand -> Text -> Vector Word8 -> m ()
setImageProfile w name profile =
  withException_ w $ useAsCString (encodeUtf8 name) $
  \cstr -> V.unsafeWith profile $
           \p -> (F.magickSetImageProfile w cstr) p (fromIntegral $ V.length profile)

-- | Returns all the profile names that match the specified pattern
-- associated with a wand.
getImageProfiles :: (MonadResource m) => Ptr MagickWand -> Text -> m [Text]
getImageProfiles w pattern = liftIO $ alloca $ \pn -> do
  pprofileps <- useAsCString (encodeUtf8 pattern) (\cstr -> F.magickGetImageProfiles w cstr pn)
  n <- fromIntegral <$> peek pn
  profileps <- peekArray n pprofileps
  profiles <- forM profileps $ \profilep -> do
    profile <- decodeUtf8 <$> packCString profilep
    F.magickRelinquishMemory (castPtr profilep)
    return profile
  F.magickRelinquishMemory (castPtr pprofileps)
  return profiles

-- | MagickGetColorspace() gets the wand colorspace type.
getColorspace :: (MonadResource m) => PMagickWand -> m ColorspaceType
getColorspace = liftIO . F.magickGetColorspace

-- | MagickSetColorspace() sets the wand colorspace type.
setColorspace :: (MonadResource m) => PMagickWand -> ColorspaceType -> m ()
setColorspace w c = withException_ w $ F.magickSetColorspace w c

getCompression :: (MonadResource m) => PMagickWand -> m CompressionType
getCompression = liftIO . F.magickGetCompression

setCompression :: (MonadResource m) => PMagickWand -> CompressionType -> m ()
setCompression w c = withException_ w $ F.magickSetCompression w c

getCompressionQuality :: (MonadResource m) => PMagickWand -> m Int
getCompressionQuality w = liftIO $ F.magickGetCompressionQuality w >>= return . fromIntegral

setCompressionQuality :: (MonadResource m) => PMagickWand -> Int -> m ()
setCompressionQuality w c  = withException_ w $ F.magickSetCompressionQuality w (fromIntegral c)

getImageResolution :: (MonadResource m) => PMagickWand -> m (Double,Double)
getImageResolution w = liftIO $ alloca $ \py -> do
  x <- alloca $ \px -> withExceptionIO w $ do
    result <- F.magickGetImageResolution w px py
    value <- peek px
    return (result, value)
  y <- peek py
  return (realToFrac x, realToFrac y)

setImageResolution :: (MonadResource m) => PMagickWand -> Double -> Double -> m ()
setImageResolution w x y  = withException_ w $ F.magickSetImageResolution w (realToFrac x) (realToFrac y)

getImageArtifacts :: (MonadResource m) => Ptr MagickWand -> Text -> m [Text]
getImageArtifacts w pattern = liftIO $ alloca $ \pn -> do
  partifactps <- useAsCString (encodeUtf8 pattern) (\cstr -> F.magickGetImageArtifacts w cstr pn)
  n <- fromIntegral <$> peek pn
  artifactps <- peekArray n partifactps
  artifacts <- forM artifactps $ \artifactp -> do
    artifact <- decodeUtf8 <$> packCString artifactp
    F.magickRelinquishMemory (castPtr artifactp)
    return artifact
  F.magickRelinquishMemory (castPtr partifactps)
  return artifacts