{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, OverloadedStrings #-}
module Facebook.Graph
    ( getObject
    , postObject
    , Id(..)
    , searchObjects
    , SearchResultPage(..)
    ) where


import Control.Applicative
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad (mzero)
import Data.ByteString.Char8 (ByteString)
-- import Data.Text (Text)
import Data.Typeable (Typeable)

-- import qualified Control.Exception.Lifted as E
import qualified Data.Aeson as A
import qualified Data.Conduit as C
-- import qualified Data.Text as T
import qualified Network.HTTP.Conduit as H
import qualified Network.HTTP.Types as HT


import Facebook.Types
import Facebook.Monad
import Facebook.Base


-- | Make a raw @GET@ request to Facebook's Graph API.
getObject :: (C.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>
             ByteString     -- ^ Path (should begin with a slash @\/@)
          -> [Argument]     -- ^ Arguments to be passed to Facebook
          -> Maybe (AccessToken anyKind) -- ^ Optional access token
          -> FacebookT anyAuth m a
getObject path query mtoken =
  runResourceInFb $
    asJson =<< fbhttp =<< fbreq path mtoken query


-- | Make a raw @POST@ request to Facebook's Graph API.
postObject :: (C.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>
              ByteString          -- ^ Path (should begin with a slash @\/@)
           -> [Argument]          -- ^ Arguments to be passed to Facebook
           -> AccessToken anyKind -- ^ Access token
           -> FacebookT Auth m a
postObject path query token =
  runResourceInFb $ do
    req <- fbreq path (Just token) query
    asJson =<< fbhttp req { H.method = HT.methodPost }


-- | The identification code of an object.
newtype Id = Id { idCode :: ByteString }
    deriving (Eq, Ord, Show, Read, Typeable)

instance A.FromJSON Id where
    parseJSON (A.Object v) = Id <$> v A..: "id"
    parseJSON other        = Id <$> A.parseJSON other


-- | Make a raw @GET@ request to the /search endpoint of Facebook’s
-- Graph API.  Returns a raw JSON 'A.Value'.
searchObjects :: (C.MonadResource m, MonadBaseControl IO m, A.FromJSON a)
              => ByteString            -- ^ A Facebook object type to search for
              -> ByteString            -- ^ The keyword to search for
              -> [Argument]            -- ^ Additional arguments to pass
              -> Maybe UserAccessToken -- ^ Optional access token
              -> FacebookT anyAuth m (SearchResultPage a)
searchObjects objectType keyword query = getObject "/search" query'
  where query' = ("q", keyword) : ("type", objectType) : query


-- | The result object for searchObjects. The type parameter is
-- expected to be an instance of A.FromJSON, but nothing has been done
-- to assure that Facebook will return the type expected.
data SearchResultPage a = SearchResultPage
                          { searchResults :: [a]
                          , searchPage :: Maybe Pager
                          } deriving (Eq, Ord, Show, Read, Typeable)

instance (A.FromJSON a) => A.FromJSON (SearchResultPage a) where
  parseJSON (A.Object v) = SearchResultPage
                           <$> v A..: "data"
                           <*> v A..:? "paging"
  parseJSON _ = mzero


-- | Simply wraps potential links for previous and next pages within a
-- search result set.  TODO: Replace with a type that encapsulates the
-- paging requests instead of just their URLs?
data Pager = Pager { previousPage :: Maybe ByteString
                   , nextPage :: Maybe ByteString
                   } deriving (Eq, Ord, Show, Read, Typeable)

instance A.FromJSON Pager where
  parseJSON (A.Object v) = Pager
                           <$> v A..:? "previous"
                           <*> v A..:? "next"
  parseJSON _ = mzero