{-# LANGUAGE FlexibleContexts, OverloadedStrings #-}
module Facebook.FQL
    ( fqlQuery
    , FQLTime(..)
    , FQLList(..)
    , FQLObject(..)
    ) where

import Control.Applicative((<$>))
import Control.Monad.Trans.Control (MonadBaseControl)
import Data.Monoid (mempty)
import Data.Text (Text)
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)

import qualified Data.Aeson as A
import qualified Data.Conduit as C
import qualified Data.HashMap.Strict as HMS

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


-- | Query the Facebook Graph using FQL.
fqlQuery :: (C.MonadResource m, MonadBaseControl IO m, A.FromJSON a) =>
            Text                        -- ^ FQL Query
         -> Maybe (AccessToken anyKind) -- ^ Optional access token
         -> FacebookT anyAuth m (Pager a)
fqlQuery fql mtoken =
  runResourceInFb $ do
    let query = ["q" #= fql]
    asJson =<< fbhttp =<< fbreq "/fql" mtoken query


-- | @newtype@ wrapper around 'UTCTime' that is able to parse
-- FQL's time representation as seconds since the Unix epoch.
newtype FQLTime = FQLTime { unFQLTime :: UTCTime }
  deriving (Eq, Ord, Show)

instance A.FromJSON FQLTime where
  parseJSON = fmap ( FQLTime
                   . posixSecondsToUTCTime
                   . fromInteger)
            . A.parseJSON


-- | @newtype@ wrapper around lists that works around FQL's
-- strange lists.
--
-- For example, if you fetch the @tagged_uids@ field from
-- @location_post@, you'll find that Facebook's FQL represents an
-- empty list of tagged UIDs as plain JSON array (@[]@).
-- However, it represents a singleton list as an object
-- @{\"1234\": 1234}@ instead of the much more correct @[1234]@.
--
-- On the other hand, not all FQL arrays are represented in this
-- bogus manner.  Also, some so-called arrays by FQL's
-- documentation are actually objects, see 'FQLObject'.
newtype FQLList a = FQLList { unFQLList :: [a] }
  deriving (Eq, Ord, Show)

instance A.FromJSON a => A.FromJSON (FQLList a) where
  parseJSON (A.Object o) = FQLList <$> mapM A.parseJSON (HMS.elems o)
  parseJSON v            = FQLList <$> A.parseJSON v


-- | @newtype@ wrapper around any object that works around FQL's
-- strange objects.
--
-- For example, if you fetch the @app_data@ field from @stream@,
-- you'll find that empty objects are actually represented as
-- empty lists @[]@ instead of a proper empty object @{}@.  Also
-- note that FQL's documentation says that @app_data@ is an
-- array, which it clear is not.  See also 'FQLList'.
newtype FQLObject a = FQLObject { unFQLObject :: a }
  deriving (Eq, Ord, Show)

instance A.FromJSON a => A.FromJSON (FQLObject a) where
  parseJSON (A.Array a) | a == mempty = FQLObject <$> A.parseJSON (A.Object mempty)
  parseJSON v                         = FQLObject <$> A.parseJSON v