    {-# LANGUAGE CPP #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE TypeSynonymInstances #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE TupleSections #-}
    {-# LANGUAGE FlexibleInstances #-}
    -- | Main module for image import/export into various image formats.
    --
    -- To use the library without thinking about it, look after 'decodeImage' and
    -- 'readImage'.
    --
    -- Generally, the @read*@ functions read the images from a file and try to decode
    -- it, and the @decode*@ functions try to decode a bytestring.
    --
    -- For an easy image writing use the 'saveBmpImage', 'saveJpgImage' & 'savePngImage'
    -- functions
    module Codec.Picture (
                         -- * Generic functions
                           readImage
                         , readImageWithMetadata
                         , decodeImage
                         , decodeImageWithMetadata
                         , decodeImageWithPaletteAndMetadata
                         , pixelMap
                         , generateImage
                         , generateFoldImage
                         , withImage
                         , palettedToTrueColor

                          -- * RGB helper functions
                         , convertRGB8
                         , convertRGBA8

                         -- * Lens compatibility
                         , Traversal
                         , imagePixels
                         , imageIPixels

                         -- * Generic image writing
                         , saveBmpImage
                         , saveJpgImage
                         , saveGifImage
                         , savePngImage
                         , saveTiffImage
                         , saveRadianceImage

                         -- * Specific image format functions
                         -- ** Bitmap handling
                         , BmpEncodable
                         , writeBitmap
                         , encodeBitmap
                         , readBitmap
                         , decodeBitmap
                         , encodeDynamicBitmap
                         , writeDynamicBitmap

                         -- ** Gif handling
                         , readGif
                         , readGifImages
                         , decodeGif
                         , decodeGifImages

                         , encodeGifImage
                         , writeGifImage
                         , encodeGifImageWithPalette
                         , writeGifImageWithPalette
                         , encodeColorReducedGifImage
                         , writeColorReducedGifImage
                         , encodeGifImages
                         , writeGifImages

                         -- *** Gif animation
                         , GifDelay
                         , GifLooping( .. )
                         , encodeGifAnimation
                         , writeGifAnimation

                         -- ** Jpeg handling
                         , readJpeg
                         , decodeJpeg
                         , encodeJpeg
                         , encodeJpegAtQuality

                         -- ** Png handling
                         , PngSavable( .. )
                         , readPng
                         , decodePng
                         , writePng
                         , encodePalettedPng
                         , encodeDynamicPng
                         , writeDynamicPng

                         -- ** TGA handling
                         , readTGA
                         , decodeTga
                         , TgaSaveable
                         , encodeTga
                         , writeTga

                         -- ** Tiff handling
                         , readTiff
                         , TiffSaveable
                         , decodeTiff
                         , encodeTiff
                         , writeTiff

                         -- ** HDR (Radiance/RGBE) handling
                         , readHDR
                         , decodeHDR
                         , encodeHDR
                         , writeHDR

                         -- ** Color Quantization
                         , PaletteCreationMethod(..)
                         , PaletteOptions(..)
                         , palettize

                         -- * Image types and pixel types
                         -- ** Image
                         , Image( .. )
                         , DynamicImage( .. )
                         , Palette
                         -- ** Pixels
                         , Pixel( .. )
                         -- $graph
                         , Pixel8
                         , Pixel16
                         , PixelF

                         , PixelYA8( .. )
                         , PixelYA16( .. )
                         , PixelRGB8( .. )
                         , PixelRGB16( .. )
                         , PixelRGBF( .. )
                         , PixelRGBA8( .. )
                         , PixelRGBA16( .. )
                         , PixelYCbCr8( .. )
                         , PixelCMYK8( .. )
                         , PixelCMYK16( .. )

                         -- * Foreign unsafe import
                         , imageFromUnsafePtr
                         ) where

    #if !MIN_VERSION_base(4,8,0)
    import Control.Applicative( (<$>) )
    #endif

    import Control.Arrow( first )
    import Data.Bits( unsafeShiftR )
    import Control.DeepSeq( NFData, deepseq )
    import qualified Control.Exception as Exc ( catch, IOException )
    import Codec.Picture.Metadata( Metadatas )
    import Codec.Picture.Bitmap( BmpEncodable
                               , decodeBitmap
                               , decodeBitmapWithPaletteAndMetadata
                               , writeBitmap, encodeBitmap
                               , encodeDynamicBitmap, writeDynamicBitmap )
    import Codec.Picture.Jpg( decodeJpeg
                            , decodeJpegWithMetadata
                            , encodeJpeg
                            , encodeJpegAtQuality )
    import Codec.Picture.Png( PngSavable( .. )
                            , decodePng
                            , decodePngWithPaletteAndMetadata
                            , writePng
                            , encodeDynamicPng
                            , encodePalettedPng
                            , writeDynamicPng
                            )

    import Codec.Picture.Gif( GifDelay
                            , GifLooping( .. )
                            , decodeGif
                            , decodeGifWithPaletteAndMetadata
                            , decodeGifImages
                            , encodeGifImage
                            , encodeGifImageWithPalette
                            , encodeGifImages

                            , writeGifImage
                            , writeGifImageWithPalette
                            , writeGifImages
                            )

    import Codec.Picture.HDR( decodeHDR
                            , decodeHDRWithMetadata
                            , encodeHDR
                            , writeHDR
                            )
    import Codec.Picture.Tiff( decodeTiff
                             , decodeTiffWithPaletteAndMetadata
                             , TiffSaveable
                             , encodeTiff
                             , writeTiff )
    import Codec.Picture.Tga( TgaSaveable
                            , decodeTga
                            , decodeTgaWithPaletteAndMetadata
                            , encodeTga
                            , writeTga
                            )
    import Codec.Picture.Saving
    import Codec.Picture.Types
    import Codec.Picture.ColorQuant
    import Codec.Picture.VectorByteConversion( imageFromUnsafePtr )
    -- import System.IO ( withFile, IOMode(ReadMode) )
    #ifdef WITH_MMAP_BYTESTRING
    import System.IO.MMap ( mmapFileByteString )
    #endif

    import qualified Data.ByteString as B
    import qualified Data.ByteString.Lazy as L
    import qualified Data.Vector.Storable as VS

    -- | Return the first Right thing, accumulating error
    eitherLoad :: c -> [(String, c -> Either String b)] -> Either String b
    eitherLoad v = inner ""
        where inner errAcc [] = Left $ "Cannot load file\n" ++ errAcc
              inner errAcc ((hdr, f) : rest) = case f v of
                    Left  err  -> inner (errAcc ++ hdr ++ " " ++ err ++ "\n") rest
                    Right rez  -> Right rez

    -- | Encode a full color image to a gif by applying a color quantization
    -- algorithm on it.
    encodeColorReducedGifImage :: Image PixelRGB8 -> Either String L.ByteString
    encodeColorReducedGifImage img = encodeGifImageWithPalette indexed pal
      where (indexed, pal) = palettize defaultPaletteOptions img

    -- | Write a full color image to a gif by applying a color quantization
    -- algorithm on it.
    writeColorReducedGifImage :: FilePath -> Image PixelRGB8 -> Either String (IO ())
    writeColorReducedGifImage path img =
        L.writeFile path <$> encodeColorReducedGifImage img

    -- | Helper function to create a gif animation.
    -- All the images of the animation are separated
    -- by the same delay.
    encodeGifAnimation :: GifDelay -> GifLooping
                       -> [Image PixelRGB8] -> Either String L.ByteString
    encodeGifAnimation delay looping lst =
        encodeGifImages looping
            [(pal, delay, img)
                    | (img, pal) <- palettize defaultPaletteOptions <$> lst]

    -- | Helper function to write a gif animation on disk.
    -- See encodeGifAnimation
    writeGifAnimation :: FilePath -> GifDelay -> GifLooping
                      -> [Image PixelRGB8] -> Either String (IO ())
    writeGifAnimation path delay looping img =
        L.writeFile path <$> encodeGifAnimation delay looping img

    withImageDecoder :: (NFData a)
                     => (B.ByteString -> Either String a) -> FilePath
                     -> IO (Either String a)
    withImageDecoder decoder path = Exc.catch doit
                        (\e -> return . Left $ show (e :: Exc.IOException))
        where doit = force . decoder <$> get
    #ifdef WITH_MMAP_BYTESTRING
              get = mmapFileByteString path Nothing
    #else
              get = B.readFile path
    #endif
              -- force appeared in deepseq 1.3, Haskell Platform
              -- provides 1.1
              force x = x `deepseq` x

    -- | Load an image file without even thinking about it, it does everything
    -- as 'decodeImage'
    readImage :: FilePath -> IO (Either String DynamicImage)
    readImage = withImageDecoder decodeImage

    -- | Equivalent to 'readImage'  but also providing metadatas.
    readImageWithMetadata :: FilePath -> IO (Either String (DynamicImage, Metadatas))
    readImageWithMetadata = withImageDecoder decodeImageWithMetadata

    -- | If you want to decode an image in a bytestring without even thinking
    -- in term of format or whatever, this is the function to use. It will try
    -- to decode in each known format and if one decoding succeeds, it will return
    -- the decoded image in it's own colorspace.
    decodeImage :: B.ByteString -> Either String DynamicImage
    decodeImage = fmap fst . decodeImageWithMetadata

    class Decimable px1 px2 where
       decimateBitDepth :: Image px1 -> Image px2

    decimateWord16 :: ( Pixel px1, Pixel px2
                      , PixelBaseComponent px1 ~ Pixel16
                      , PixelBaseComponent px2 ~ Pixel8
                      ) => Image px1 -> Image px2
    decimateWord16 (Image w h da) =
      Image w h $ VS.map (\v -> fromIntegral $ v `unsafeShiftR` 8) da

    decimateFloat :: ( Pixel px1, Pixel px2
                     , PixelBaseComponent px1 ~ PixelF
                     , PixelBaseComponent px2 ~ Pixel8
                     ) => Image px1 -> Image px2
    decimateFloat (Image w h da) =
      Image w h $ VS.map (floor . (255*) . max 0 . min 1) da

    instance Decimable Pixel16 Pixel8 where
       decimateBitDepth = decimateWord16

    instance Decimable PixelYA16 PixelYA8 where
       decimateBitDepth = decimateWord16

    instance Decimable PixelRGB16 PixelRGB8 where
       decimateBitDepth = decimateWord16

    instance Decimable PixelRGBA16 PixelRGBA8 where
       decimateBitDepth = decimateWord16

    instance Decimable PixelCMYK16 PixelCMYK8 where
       decimateBitDepth = decimateWord16

    instance Decimable PixelF Pixel8 where
       decimateBitDepth = decimateFloat

    instance Decimable PixelRGBF PixelRGB8 where
       decimateBitDepth = decimateFloat

    -- | Convert by any mean possible a dynamic image to an image
    -- in RGBA. The process can lose precision while converting from
    -- 16bits pixels or Floating point pixels.
    convertRGBA8 :: DynamicImage -> Image PixelRGBA8
    convertRGBA8 dynImage = case dynImage of
      ImageY8     img -> promoteImage img
      ImageY16    img -> promoteImage (decimateBitDepth img :: Image Pixel8)
      ImageYF     img -> promoteImage (decimateBitDepth img :: Image Pixel8)
      ImageYA8    img -> promoteImage img
      ImageYA16   img -> promoteImage (decimateBitDepth img :: Image PixelYA8)
      ImageRGB8   img -> promoteImage img
      ImageRGB16  img -> promoteImage (decimateBitDepth img :: Image PixelRGB8)
      ImageRGBF   img -> promoteImage (decimateBitDepth img :: Image PixelRGB8)
      ImageRGBA8  img -> promoteImage img
      ImageRGBA16 img -> decimateBitDepth img
      ImageYCbCr8 img -> promoteImage (convertImage img :: Image PixelRGB8)
      ImageCMYK8  img -> promoteImage (convertImage img :: Image PixelRGB8)
      ImageCMYK16 img ->
        promoteImage (convertImage (decimateBitDepth img :: Image PixelCMYK8) :: Image PixelRGB8)

    -- | Convert by any mean possible a dynamic image to an image
    -- in RGB. The process can lose precision while converting from
    -- 16bits pixels or Floating point pixels. Any alpha layer will
    -- be dropped
    convertRGB8 :: DynamicImage -> Image PixelRGB8
    convertRGB8 dynImage = case dynImage of
      ImageY8     img -> promoteImage img
      ImageY16    img -> promoteImage (decimateBitDepth img :: Image Pixel8)
      ImageYF     img -> promoteImage (decimateBitDepth img :: Image Pixel8)
      ImageYA8    img -> promoteImage img
      ImageYA16   img -> promoteImage (decimateBitDepth img :: Image PixelYA8)
      ImageRGB8   img -> img
      ImageRGB16  img -> decimateBitDepth img
      ImageRGBF   img -> decimateBitDepth img :: Image PixelRGB8
      ImageRGBA8  img -> dropAlphaLayer img
      ImageRGBA16 img -> dropAlphaLayer (decimateBitDepth img :: Image PixelRGBA8)
      ImageYCbCr8 img -> convertImage img
      ImageCMYK8  img -> convertImage img
      ImageCMYK16 img -> convertImage (decimateBitDepth img :: Image PixelCMYK8)

    -- | Equivalent to 'decodeImage', but also provide potential metadatas
    -- present in the given file and the palettes if the format provides them.
    decodeImageWithPaletteAndMetadata :: B.ByteString -> Either String (PalettedImage, Metadatas)
    decodeImageWithPaletteAndMetadata str = eitherLoad str
        [ ("Jpeg", fmap (first TrueColorImage) . decodeJpegWithMetadata)
        , ("PNG", decodePngWithPaletteAndMetadata)
        , ("Bitmap", decodeBitmapWithPaletteAndMetadata)
        , ("GIF", decodeGifWithPaletteAndMetadata)
        , ("HDR", fmap (first TrueColorImage) . decodeHDRWithMetadata)
        , ("Tiff", decodeTiffWithPaletteAndMetadata)
        , ("TGA", decodeTgaWithPaletteAndMetadata)
        ]

    -- | Equivalent to 'decodeImage', but also provide potential metadatas
    -- present in the given file.
    decodeImageWithMetadata :: B.ByteString -> Either String (DynamicImage, Metadatas)
    decodeImageWithMetadata =
        fmap (first palettedToTrueColor) . decodeImageWithPaletteAndMetadata

    -- | Helper function trying to load a png file from a file on disk.
    readPng :: FilePath -> IO (Either String DynamicImage)
    readPng = withImageDecoder decodePng

    -- | Helper function trying to load a gif file from a file on disk.
    readGif :: FilePath -> IO (Either String DynamicImage)
    readGif = withImageDecoder decodeGif

    -- | Helper function trying to load tiff file from a file on disk.
    readTiff :: FilePath -> IO (Either String DynamicImage)
    readTiff = withImageDecoder decodeTiff

    -- | Helper function trying to load all the images of an animated
    -- gif file.
    readGifImages :: FilePath -> IO (Either String [DynamicImage])
    readGifImages = withImageDecoder decodeGifImages

    -- | Try to load a jpeg file and decompress. The colorspace is still
    -- YCbCr if you want to perform computation on the luma part. You can
    -- convert it to RGB using 'colorSpaceConversion'.
    readJpeg :: FilePath -> IO (Either String DynamicImage)
    readJpeg = withImageDecoder decodeJpeg

    -- | Try to load a .bmp file. The colorspace would be RGB, RGBA or Y.
    readBitmap :: FilePath -> IO (Either String DynamicImage)
    readBitmap = withImageDecoder decodeBitmap

    -- | Try to load a .pic file. The colorspace can only be
    -- RGB with floating point precision.
    readHDR :: FilePath -> IO (Either String DynamicImage)
    readHDR = withImageDecoder decodeHDR

    -- | Try to load a .tga file from disk.
    readTGA :: FilePath -> IO (Either String DynamicImage)
    readTGA = withImageDecoder decodeTga

    -- | Save an image to a '.jpg' file, will do everything it can to save an image.
    saveJpgImage :: Int -> FilePath -> DynamicImage -> IO ()
    saveJpgImage quality path img = L.writeFile path $ imageToJpg quality img

    -- | Save an image to a '.gif' file, will do everything it can to save it.
    saveGifImage :: FilePath -> DynamicImage -> Either String (IO ())
    saveGifImage path img = L.writeFile path <$> imageToGif img

    -- | Save an image to a '.tiff' file, will do everything it can to save an image.
    saveTiffImage :: FilePath -> DynamicImage -> IO ()
    saveTiffImage path img = L.writeFile path $ imageToTiff img

    -- | Save an image to a '.hdr' file, will do everything it can to save an image.
    saveRadianceImage :: FilePath -> DynamicImage -> IO ()
    saveRadianceImage path = L.writeFile path . imageToRadiance

    -- | Save an image to a '.png' file, will do everything it can to save an image.
    -- For example, a simple transcoder to png
    --
    -- > transcodeToPng :: FilePath -> FilePath -> IO ()
    -- > transcodeToPng pathIn pathOut = do
    -- >    eitherImg <- readImage pathIn
    -- >    case eitherImg of
    -- >        Left _ -> return ()
    -- >        Right img -> savePngImage pathOut img
    --
    savePngImage :: FilePath -> DynamicImage -> IO ()
    savePngImage path img = L.writeFile path $ imageToPng img

    -- | Save an image to a '.bmp' file, will do everything it can to save an image.
    saveBmpImage :: FilePath -> DynamicImage -> IO ()
    saveBmpImage path img = L.writeFile path $ imageToBitmap img