{-# LANGUAGE ExistentialQuantification #-}
-- {-# LANGUAGE OverloadedStrings #-}
-- {-# LANGUAGE ForeignFunctionInterface #-}
-- {-# LANGUAGE InterruptibleFFI #-}
-- {-# LANGUAGE EmptyDataDecls #-}

-- |
-- Uniform-IO provides a typeclass for uniform access of different types of targets,
-- and implementations for abstracting standard streams, files and network connections.
-- This module also provides TLS wraping over other IO targets.
module System.IO.Uniform (
  UniformIO(..),
  TlsSettings(..),
  SomeIO(..), TlsIO,
  mapOverInput
  ) where

import System.IO.Uniform.External

import Foreign
--import Foreign.C.Types
--import Foreign.C.String
import Foreign.C.Error
--import qualified Data.IP as IP
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
--import qualified Data.ByteString.Lazy as LBS
--import qualified Data.ByteString.Builder as BSBuild
--import qualified Data.List as L
import Control.Exception
import Control.Applicative ((<$>))
--import Data.Monoid (mappend)
--import qualified Network.Socket as Soc
import System.IO.Error
--import Control.Concurrent.MVar

import Data.Default.Class

import System.Posix.Types (Fd(..))

-- |
-- Typeclass for uniform IO targets.
class UniformIO a where
  -- | uRead fd n
  --
  --  Reads a block of at most n bytes of data from the IO target.
  --  Reading will block if there's no data available, but will return immediately
  --  if any amount of data is availble.
  --
  --  Must thow System.IO.Error.EOFError if reading beihond EOF.
  uRead  :: a -> Int -> IO ByteString
  -- | uPut fd text
  --
  --  Writes all the bytes of text into the IO target. Takes care of retrying if needed.
  uPut   :: a -> ByteString -> IO ()
  -- | fClose fd
  --
  --  Closes the IO target, releasing any allocated resource. Resources may leak if not called
  --  for every oppened fd.
  uClose :: a -> IO ()
  -- | startTLS fd
  --
  --  Starts a TLS connection over the IO target.
  startTls :: TlsSettings -> a -> IO TlsIO
  -- | isSecure fd
  --
  --  Indicates whether the data written or read from fd is secure at transport.
  isSecure :: a -> Bool
  
-- | A type that wraps any type in the UniformIO class.
data SomeIO = forall a. (UniformIO a) => SomeIO a

instance UniformIO SomeIO where
  uRead (SomeIO s) n = uRead s n
  uPut (SomeIO s) t  = uPut s t
  uClose (SomeIO s) = uClose s
  startTls set (SomeIO s) = startTls set s
  isSecure (SomeIO s) = isSecure s

-- | Settings for starttls functions.
data TlsSettings = TlsSettings {tlsPrivateKeyFile :: String, tlsCertificateChainFile :: String, tlsDHParametersFile :: String} deriving (Read, Show)

instance Default TlsSettings where
  def = TlsSettings "" "" ""
  
-- | UniformIO wrapper that applies TLS to communication on IO target.
-- This type is constructed by calling startTls on other targets.
instance UniformIO TlsIO where
  uRead s n = do
    allocaArray n (
      \b -> do
        count <- c_recvTls (tls s) b $ fromIntegral n
        if count < 0
          then throwErrno "could not read"
          else BS.packCStringLen (b, fromIntegral count)
      )
  uPut s t = do
    BS.useAsCStringLen t (
      \(str, n) -> do
        count <- c_sendTls (tls s) str $ fromIntegral n
        if count < 0
          then throwErrno "could not write"
          else return ()
      )
  uClose s = do
    d <- c_closeTls (tls s)
    f <- Fd <$> c_prepareToClose d
    closeFd f
  startTls _ s = return s
  isSecure _ = True


-- | mapOverInput io block_size f initial
--   Reads io untill the end of file, evaluating a(i) <- f a(i-1) read_data
--   where a(0) = initial and the last value after io reaches EOF is returned.
--
--   Notice that the length of read_data might not be equal block_size.
mapOverInput :: forall a io. UniformIO io => io -> Int -> (a -> ByteString -> IO a) -> a -> IO a
mapOverInput io block f initial = do
  a <- tryIOError $ uRead io block
  case a of
    Left e -> if isEOFError e then return initial else throw e -- EOF
    Right dt -> do
      i <- f initial dt
      mapOverInput io block f i