    --------------------------------------------------------------------
    -- |
    -- Module    : Network.Curl.Download.Lazy
    -- Copyright : (c) Don Stewart
    -- License   : BSD3
    --
    -- Maintainer:  Don Stewart <dons@galois.com>
    -- Stability :  provisional
    -- Portability: posix
    --
    -- A binding to curl, an efficient, high level library for
    -- retrieving files using Uniform Resource Locators (URLs).
    --
    -- Content may be retrieved as a lazy "ByteString".
    --
    -- Error handling is encapsulated in the "Either" type.
    --
    --------------------------------------------------------------------

    module Network.Curl.Download.Lazy (

            -- * The basic lazy interface to network content
              openLazyURI
            , openLazyURIWithOpts

        ) where

    import Network.Curl
    import Foreign

    import Data.IORef

    import qualified Data.ByteString.Lazy.Internal as L
    import qualified Data.ByteString.Internal      as S

    ------------------------------------------------------------------------

    -- | Download content specified by a url using curl, returning the
    -- content as a lazy "ByteString".
    --
    -- If an error occurs, "Left" is returned, with a
    -- protocol-specific error string.
    --
    -- Examples:
    --
    -- > openLazyURI "http://haskell.org"
    --
    openLazyURI :: String -> IO (Either String L.ByteString)
    openLazyURI s = openLazyURIWithOpts [] s

    -- | Like openURI, but takes curl options.
    --
    -- Examples:
    --
    -- > openLazyURIWithOpts [CurlPost True] "http://haskell.org"
    --
    openLazyURIWithOpts :: [CurlOption] -> String -> IO (Either String L.ByteString)
    openLazyURIWithOpts opts s = case parseURL s of
         Nothing  -> return $ Left $ "Malformed url: "++ s
         Just url -> do
            e <- getFile url opts
            return $ case e of
                 Left err   -> Left $ "Failed to connect: " ++ err
                 Right src  -> Right src

    ------------------------------------------------------------------------
    -- Internal:
    --

    newtype URL = URL String

    parseURL :: String -> Maybe URL
    parseURL s = Just (URL s) -- no parsing

    getFile :: URL -> [CurlOption] -> IO (Either String L.ByteString)
    getFile (URL url) flags = do
        h   <- initialize
        ref <- newIORef L.Empty

        setopt h (CurlFailOnError True)
        setDefaultSSLOpts h url
        setopt h (CurlURL url)
        setopt h (CurlWriteFunction (gather ref))
        mapM_ (setopt h) flags
        rc         <- perform h
        chunks     <- readIORef ref

        return $ if rc /= CurlOK
            then Left (show rc)
            else Right $! rev'spine chunks

    --          fp <- newForeignPtr finalizerFree buf'
    --          return (Right $! S.fromForeignPtr fp 0 (fromIntegral sz))

    gather :: IORef L.ByteString -> WriteFunction
    gather r = writer $ \chunk -> do
        chunks <- readIORef r
        let chunks' = L.Chunk chunk chunks
        writeIORef r $! chunks'

    -- memcpy chunks of data into our bytestring.
    writer :: (S.ByteString -> IO ()) -> WriteFunction
    writer f src sz nelems _ = do
        let n' = sz * nelems
        f =<< (S.create (fromIntegral n') $
                \dest -> S.memcpy dest (castPtr src) (fromIntegral n'))
        return n'

    -- reverse just the spine of a lazy bytestring
    rev'spine :: L.ByteString -> L.ByteString
    rev'spine l =  rev l L.Empty
      where
        rev L.Empty a        = a
        rev (L.Chunk x xs) a = rev xs (L.Chunk x a)