{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls #-}
--------------------------------------------------------------------
-- |
-- Module    : Network.Curl.Types
-- Copyright : (c) Galois Inc 2007-2009
-- License   : BSD3
--
-- Maintainer: Sigbjorn Finne <sof@galois.com>
-- Stability : provisional
-- Portability: portable
--
-- Basic set of types for the Haskell curl binding, including the
-- @Curl@ handle type which holds the C library stateful connection
-- handle along with a set of cleanup actions tht should be performed
-- upon shutting down the curl session.
--
--------------------------------------------------------------------
module Network.Shpider.Curl.Types
  ( CurlH, URLString, Port, Long, LLong, Slist_ 
  , Curl, curlPrim, mkCurl, mkCurlWithCleanup
  , OptionMap, shareCleanup, runCleanup, updateCleanup
  , manual_cleanup , mkCurl_no_cleanup
  ) where

import Network.Shpider.Curl.Debug

import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.Storable
import Data.Word
import Control.Concurrent
import Control.Monad.Fix(mfix)
import Data.Maybe(fromMaybe)
import qualified Data.IntMap as M
import Data.IORef
import System.IO

data Curl_
type CurlH    = Ptr Curl_

type URLString = String
type Port = Long
type Long = Word32
type LLong = Word64
data Slist_


data Curl = Curl 
  { curlH       :: MVar (ForeignPtr Curl_)  -- libcurl is not thread-safe.
  , curlCleanup :: IORef OptionMap          -- deallocate Haskell curl data 
  }  


-- | Execute a "primitve" curl operation.
-- NOTE: See warnings about the use of 'withForeginPtr'.
curlPrim :: Curl -> (IORef OptionMap -> CurlH -> IO a) -> IO a
curlPrim c f  = withMVar (curlH c) $ \ h ->
                withForeignPtr h   $ f $ curlCleanup c


-- | Allocates a Haskell handle from a C handle.
-- | No garbage collection so cookies are written deterministically
mkCurl_no_cleanup :: CurlH -> IO Curl
mkCurl_no_cleanup h = do
  debug "ALLOC: CURL"
  fh  <- newForeignPtr_ h 
  v1  <- newMVar fh
  v2  <- newIORef om_empty 
  let new_h = Curl { curlH = v1, curlCleanup = v2 }
  return new_h

-- | Allocates a Haskell handle from a C handle.
mkCurl :: CurlH -> IO Curl
mkCurl h = 
    mkCurlWithCleanup h om_empty

-- | Allocates a Haskell handle from a C handle.
mkCurlWithCleanup :: CurlH -> OptionMap -> IO Curl
mkCurlWithCleanup h clean = do
  debug "ALLOC: CURL"
  fh  <- newForeignPtr_ h 
  v1  <- newMVar fh
  v2  <- newIORef clean
  let new_h = Curl { curlH = v1, curlCleanup = v2 }
 
  fin <- mkIOfin $ do debug "FREE: CURL"
                      runCleanup v2
                      withForeignPtr fh easy_cleanup
                      
  addForeignPtrFinalizer fin fh

  return new_h

 -- Manually cleanup a curl instance, writing cookies etc.
manual_cleanup curl = do 
   fh <- readMVar $ curlH curl
   debug "FREE: CURL"
   runCleanup $ curlCleanup curl
   withForeignPtr fh easy_cleanup

-- Admin code for cleaning up marshalled data.
-- Note that these functions assume that they are running atomically,
-- so access to them should be protected by a lock.
--------------------------------------------------------------------------------
runCleanup     :: IORef OptionMap -> IO ()
runCleanup r    = do m <- readIORef r
                     om_cleanup m
                     writeIORef r om_empty 

shareCleanup  :: IORef OptionMap -> IO OptionMap
shareCleanup r  = do old <- readIORef r
                     new <- om_dup old
                     writeIORef r new
                     return new

updateCleanup :: IORef OptionMap -> Int -> IO () -> IO ()
updateCleanup r option act = writeIORef r =<< om_set option act =<< readIORef r



-- Maps that associate curl options with IO actions to
-- perform cleanup for them.
--------------------------------------------------------------------------------
type OptionMap = M.IntMap (IO ())

-- | An empty option map.
om_empty :: OptionMap
om_empty = M.empty

-- | Set the IO action for an option,
-- executing the previvous action, if there was one.
om_set :: Int -> IO () -> OptionMap -> IO OptionMap
om_set opt new_act old_map = 
  do fromMaybe (return ()) old_act
     return new_map
  where
  (old_act,new_map) = M.insertLookupWithKey (\_ a _ -> a) opt new_act old_map

-- | Execute all IO actions in the map.
om_cleanup :: OptionMap -> IO ()
om_cleanup m = sequence_ (M.elems m)

-- | Replace the actions in a map, with actions that
-- will only be executed the second time they are invoked.
om_dup :: OptionMap -> IO OptionMap
om_dup old_map = M.fromList `fmap` mapM dup (M.assocs old_map)
  where dup (x,old_io)  = do new_io <- shareIO old_io
                             return (x,new_io)

-- Share a cleanup action.  When we share cleanup duty between two handles
-- we need to ensure that the first handle to perform the cleanup will do
-- nothing (because the other handle still needs the resources).
shareIO :: IO () -> IO (IO ())
shareIO act = 
  do v <- newMVar False
     let new_act = do b <- takeMVar v
                      if b then act else putMVar v True
     return new_act
--------------------------------------------------------------------------------


-- FFI for inalizers.

-- | Make a finalizer from an IO action.
mkIOfin :: IO a -> IO (FinalizerPtr b)
mkIOfin m = mfix (\ptr -> ioFinalizer (m >> freeHaskellFunPtr ptr))

foreign import ccall 
  "curl/curl.h curl_easy_cleanup" easy_cleanup :: CurlH -> IO ()

foreign import ccall "wrapper"
  ioFinalizer :: IO () -> IO (FinalizerPtr a)


