    module Graphics.GD.ByteString.Lazy (
                        -- * Types
                        Image, Size, Point, Color,
                        -- * Creating and copying images
                        GD.newImage, GD.copyImage,
                        GD.copyRegion, GD.copyRegionScaled,
                        -- * Memory management
                        GD.withImage,
                        -- * Loading images
                        -- ** JPEG
                        loadJpegFile, loadJpegData, loadJpegByteString,
                        -- ** PNG
                        loadPngFile, loadPngData, loadPngByteString,
                        -- ** GIF
                        loadGifFile, loadGifData, loadGifByteString,
                        -- * Saving images
                        -- ** JPEG
                        saveJpegFile, saveJpegByteString,
                        -- ** PNG
                        savePngFile, savePngByteString,
                        -- ** GIF
                        saveGifFile, saveGifByteString,
                        -- * Getting image information
                        GD.imageSize,
                        -- * Querying
                        GD.getPixel,
                        -- * Manipulating images
                        GD.resizeImage, GD.rotateImage,
                        -- * Drawing
                        GD.fillImage,
                        GD.drawFilledRectangle,
                        GD.drawFilledEllipse,
                        GD.drawLine,
                        GD.drawArc,
                        GD.antiAliased,
                        GD.setPixel,
                        -- * Text
                        GD.useFontConfig,
                        drawString,
                        measureString,
                        drawStringCircle,
                        -- * Colors
                        GD.rgb, GD.rgba, GD.toRGBA
                       ) where

    import           Graphics.GD.Internal     (Point,Color,Image,GDImage,CFILE,Size)
    import qualified Graphics.GD.Internal     as GD

    import qualified Data.ByteString          as B
    import qualified Data.ByteString.Lazy     as L
    import qualified Data.ByteString.Internal as BI

    import           Control.Monad            (liftM,unless)
    import           Foreign                  (Ptr)
    import qualified Foreign                  as F
    import           Foreign                  (peek)
    import           Foreign.C                (CInt)
    import           Foreign.C                (peekCAString,peekCAString)

    --
    -- * Loading images
    --

    -- | Load a JPEG image from a file.
    loadJpegFile :: FilePath -> IO Image
    loadJpegFile = loadImageFile GD.gdImageCreateFromJpeg

    -- | Load a JPEG image from a buffer.
    loadJpegData :: Int   -- ^ Buffer size.
                 -> Ptr a -- ^ Buffer with image data.
                 -> IO Image
    loadJpegData = loadImageData GD.gdImageCreateFromJpegPtr

    -- | Load a JPEG image from a ByteString
    loadJpegByteString :: L.ByteString -> IO Image
    loadJpegByteString = onByteStringData loadJpegData

    -- | Load a PNG image from a file.
    loadPngFile :: FilePath -> IO Image
    loadPngFile = loadImageFile GD.gdImageCreateFromPng

    -- | Load a PNG image from a buffer.
    loadPngData :: Int   -- ^ Buffer size.
                -> Ptr a -- ^ Buffer with image data.
                -> IO Image
    loadPngData = loadImageData GD.gdImageCreateFromPngPtr

    -- | Load a PNG image from a ByteString
    loadPngByteString :: L.ByteString -> IO Image
    loadPngByteString = onByteStringData loadPngData

    -- | Load a GIF image from a file.
    loadGifFile :: FilePath -> IO Image
    loadGifFile = loadImageFile GD.gdImageCreateFromGif

    -- | Load a GIF image from a buffer.
    loadGifData :: Int   -- ^ Buffer size.
                -> Ptr a -- ^ Buffer with image data.
                -> IO Image
    loadGifData = loadImageData GD.gdImageCreateFromGifPtr

    -- | Load a GIF image from a ByteString
    loadGifByteString :: L.ByteString -> IO Image
    loadGifByteString = onByteStringData loadGifData

    loadImageFile :: (Ptr CFILE -> IO (Ptr GDImage)) -> FilePath -> IO Image
    loadImageFile f file = do
      p <- F.throwIfNull ("Loading image from " ++ file) $ GD.withCFILE file "rb" f
      GD.mkImage p

    loadImageData :: (CInt -> Ptr a -> IO (Ptr GDImage)) -> Int -> Ptr a -> IO Image
    loadImageData f sz buf =
        do p <- F.throwIfNull ("Loading image") $ f (fromIntegral sz) buf
           GD.mkImage p

    onByteStringData :: (Int -> Ptr a -> IO b) -> L.ByteString -> IO b
    onByteStringData f bstr =
      case BI.toForeignPtr (lazyToStrict bstr) of
        (fptr, start, sz) -> F.withForeignPtr fptr $
                               \ptr -> f sz (F.plusPtr ptr start)
      where lazyToStrict :: L.ByteString -> B.ByteString
            lazyToStrict = foldr1 B.append . L.toChunks

    --
    -- * Saving images
    --

    -- | Save an image as a JPEG file.
    saveJpegFile :: Int -- ^ quality: 0-95, or negative for default quality.
                 -> FilePath -> Image -> IO ()
    saveJpegFile q = saveImageFile (\p h -> GD.gdImageJpeg p h (fromIntegral q))

    -- | Write a JPEG format ByteString of an image.
    saveJpegByteString :: Int -> Image -> IO L.ByteString
    saveJpegByteString q =
      saveImageByteString (\p h -> GD.gdImageJpegPtr p h (fromIntegral q))

    -- | Save an image as a PNG file.
    savePngFile :: FilePath -> Image -> IO ()
    savePngFile = saveImageFile GD.gdImagePng

    -- | Write a PNG format ByteString of an image.
    savePngByteString :: Image -> IO L.ByteString
    savePngByteString = saveImageByteString GD.gdImagePngPtr

    -- | Save an image as a GIF file.
    saveGifFile :: FilePath -> Image -> IO ()
    saveGifFile = saveImageFile GD.gdImageGif

    -- | Write a GIF format ByteString of an image.
    saveGifByteString :: Image -> IO L.ByteString
    saveGifByteString = saveImageByteString GD.gdImageGifPtr

    saveImageFile :: (Ptr GDImage -> Ptr CFILE -> IO ()) -> FilePath -> Image
                     -> IO ()
    saveImageFile f file i = GD.withImagePtr i (\p -> GD.withCFILE file "wb" (f p))

    saveImageByteString :: (Ptr GDImage -> Ptr CInt -> IO (Ptr a)) -> Image
                           -> IO (L.ByteString)
    saveImageByteString f img = GD.withImagePtr img (\p -> dataByteString (f p))

    dataByteString :: (Ptr CInt -> IO (Ptr a)) -> IO L.ByteString
    dataByteString f = F.alloca $ \szPtr -> do
      datPtr <- f szPtr >>= F.newForeignPtr GD.gdFree . F.castPtr
      liftM (L.fromChunks . return . BI.fromForeignPtr datPtr 0 . fromIntegral)
            (peek szPtr)

    --
    -- * Text
    --

    -- | Draw a string using the FreeType 2.x library
    drawString :: B.ByteString -- ^ Font name
               -> Double       -- ^ Font point size
               -> Double       -- ^ Angle in counterclockwise radians
               -> Point        -- ^ Origin
               -> B.ByteString -- ^ Text, including HTML entities
               -> Color -> Image
               -> IO (Point, Point, Point, Point) -- ^ Bounding box
                                                  -- of the drawn
                                                  -- text
    drawString fontName ptSize angle (oriX, oriY) txt color img =
      GD.withImagePtr img $ drawStringImagePtr color fontName ptSize angle
                                               (oriX, oriY) txt

    -- | Measure a string using the FreeType 2.x library.  This computes
    -- the bounding box but does not actually draw the string to any
    -- image.
    measureString :: B.ByteString -- ^ Font name
                  -> Double       -- ^ Font point size
                  -> Double       -- ^ Angle in counterclockwise radians
                  -> Point        -- ^ Origin
                  -> B.ByteString -- ^ Text, including HTML entities
                  -> Color
                  -> IO (Point, Point, Point, Point) -- ^ Bounding
                                                     -- box of the
                                                     -- drawn text
    measureString fontName ptSize angle (oriX, oriY) txt color
        = drawStringImagePtr color fontName ptSize angle (oriX, oriY) txt F.nullPtr

    drawStringImagePtr :: Color -> B.ByteString -> Double -> Double -> Point
                          -> B.ByteString -> Ptr GDImage
                          -> IO (Point, Point, Point, Point)
    drawStringImagePtr color fontName ptSize angle (oriX, oriY) txt imgPtr
        = F.allocaArray 8 $
          \bboxPtr -> B.useAsCString fontName $
          \cFontName -> B.useAsCString txt $
          \cTxt -> do res <- GD.gdImageStringFT imgPtr bboxPtr color cFontName
                                                (GD.double ptSize) (GD.double angle)
                                                (GD.int oriX) (GD.int oriY) cTxt
                      if res == F.nullPtr
                         then F.peekArray 8 bboxPtr >>= parseBBox
                         else peekCAString res >>= ioError . userError
        where parseBBox l =
                case map GD.int l of
                  [llx, lly, lrx, lry, urx, ury, ulx, uly] ->
                    return ((llx, lly), (lrx, lry), (urx, ury), (ulx, uly))
                  _ -> ioError $ userError $
                         "parseBBox with /= 8 elements: " ++ show l

    -- | Draw strings around the top and bottom of a torus
    drawStringCircle :: Point        -- ^ Center of text path circle
                     -> Double       -- ^ Outer radius of text
                     -> Double       -- ^ Fraction of radius occupied by text
                     -> Double       -- ^ Portion of circle arc filled by text
                     -> B.ByteString -- ^ Font name
                     -> Double       -- ^ Font size hint
                     -> B.ByteString -- ^ Text to write on the top of the circle
                     -> B.ByteString -- ^ Text to write on the bottom of the circle
                     -> Color        -- ^ Text color
                     -> Image -> IO ()
    drawStringCircle (ctrX, ctrY) rad textRad textFill fontName fontSize topTxt
                     bottomTxt color img
        = B.useAsCString fontName $
          \cFontName -> B.useAsCString topTxt $
          \cTopTxt -> B.useAsCString bottomTxt $
          \cBottomTxt -> GD.withImagePtr img $
          \imgPtr -> do
            res <- GD.gdImageStringFTCircle imgPtr (GD.int ctrX) (GD.int ctrY)
                                            (GD.double rad) (GD.double textRad)
                                            (GD.double textFill) cFontName
                                            (GD.double fontSize) cTopTxt
                                            cBottomTxt  color
            unless (res == F.nullPtr) (peekCAString res >>= ioError . userError)