{-# LANGUAGE ScopedTypeVariables #-}
module Imm.HTTP where

-- {{{ Imports
import Imm.Error
import Imm.Util

import Control.Exception as E
import Control.Monad.Error hiding(forM_, mapM_)
import Control.Monad.Trans.Resource

import Data.ByteString as B
import Data.ByteString.Lazy as BL
import Data.ByteString.Char8 as BC
import Data.CaseInsensitive
import Data.Text.ICU.Convert
import qualified Data.Text.Lazy as TL

import Network.HTTP.Conduit as H
import Network.URI
-- }}}

-- {{{ Types
class (Applicative m, Functor m, Monad m) => Decoder m where
    converter :: m Converter
    decode    :: BL.ByteString -> m TL.Text
    decode string = return . TL.fromChunks . (: []) =<< toUnicode <$> converter <*> return strictString
      where
        strictString = B.concat $ BL.toChunks string

instance (Error e, Decoder m) => Decoder (ErrorT e m) where
    converter = lift converter

-- | HTTP client
-- data Client = Client {
    -- _manager       :: Manager,
    -- _state         :: BrowserState}

-- makeLenses ''Client

-- run :: (MonadBase IO m) => ReaderT (IORef Client) m a -> m a
-- run f = do
    -- theInput <- newEmptyMVar
    -- threadID <- fork worker
    -- manager' <- io $ newManager def
    -- ref      <- io . newIORef $ Client manager' (defaultState manager')
    -- result   <- runReaderT f ref
    -- io $ closeManager manager'
    -- return result

-- worker = do
    -- (theOrder, theResultMVar) <- takeMVar order
    -- putMVar theResultMVar result
    -- result <- work theOrder

-- }}}

-- getRaw = do
    -- putMVar

-- | Perform an HTTP GET request and return the response body as raw 'ByteString'
getRaw :: (MonadBase IO m, MonadError ImmError m) => URI -> m BL.ByteString
getRaw uri = do
    req <- request $ show uri
    res <- withManager' (httpLbs req)
    return $ responseBody res

-- | Same as 'getRaw' with additional decoding
get :: (Decoder m, MonadBase IO m, MonadError ImmError m) => URI -> m TL.Text
get uri = decode =<< getRaw uri

-- | Monad-agnostic version of 'withManager'
withManager' :: (MonadError ImmError m, MonadBase IO m) => (Manager -> ResourceT IO b) -> m b
withManager' f = do
    res <- timeout 11000000 $ (Right <$> withManager f) `catch` (return . Left . IOE) `catch` (return . Left . HTTPError) `catch` (return . Left . TLSError)
    either throwError return res

-- | Monad-agnostic version of 'parseUrl'
parseURL :: (MonadBase IO m, MonadError ImmError m) => String -> m (Request m')
parseURL uri = do
    result <- io $ (Right <$> parseUrl uri) `catch` (return . Left . HTTPError)
    either throwError return result

-- | Build an HTTP request for given URI
request :: (MonadBase IO m, MonadError ImmError m) => String -> m (Request a)
request uri = do
    req <- parseURL uri
    return $ req { requestHeaders = [
        (mk $ BC.pack "User-Agent", BC.pack "Mozilla/4.0"),
        (mk $ BC.pack "Accept", BC.pack "*/*")]}