{-# LANGUAGE ScopedTypeVariables, TemplateHaskell #-}
-- | Response module
{-# OPTIONS_HADDOCK not-home #-}
module Network.Lastfm
  ( Lastfm, Response, ResponseType(..)
  , callAPI, callAPIsigned
  , xml, json
  , module Network.Lastfm.Types
  ) where

import Codec.Binary.UTF8.String (encodeString)
import Control.Applicative ((<$>))
import Control.Arrow (second)
import Data.ByteString.Lazy.Char8 (ByteString)
import Data.Digest.Pure.MD5 (md5)
import Data.Function (on)
import Data.List (sortBy)
import Data.URLEncoded (urlEncode, export)
import Language.Haskell.TH
import Network.Curl
import Network.Lastfm.Error
import Network.Lastfm.Types
import qualified Data.ByteString.Lazy.Char8 as BS

-- | Type synonym for Lastfm response or error.
type Lastfm a = IO (Either LastfmError a)
-- | Type synonym for Lastfm response
type Response = ByteString
-- | Desired type of Lastfm response
data ResponseType = XML | JSON

-- | Low level function. Sends POST query to Lastfm API.
callAPI  ResponseType  [(String, String)]  Lastfm Response
callAPI t = query (parseError t) . insertType t . map (second encodeString)

-- | Low level function. Sends signed POST query to Lastfm API.
callAPIsigned  ResponseType  Secret  [(String, String)]  Lastfm Response
callAPIsigned t (Secret s) xs = query (parseError t) zs
  where ys = map (second encodeString) . filter (not . null . snd) $ xs
        zs = insertType t $ ("api_sig", sign ys) : ys

        sign  [(String, String)]  String
        sign = show . md5 . BS.pack . (++ s) . concatMap (uncurry (++)) . sortBy (compare `on` fst)

insertType  ResponseType  [(String, String)]  [(String, String)]
insertType XML = id
insertType JSON = (("format", "json") :)

parseError  ResponseType  ByteString  Maybe LastfmError
parseError XML = xmlError
parseError JSON = jsonError

query  (ByteString  Maybe LastfmError)  [(String, String)]  IO (Either LastfmError Response)
query γ xs = curlResponse xs >>= \r 
  return $ case γ r of
    Just n  Left n
    Nothing  Right r

curlResponse  [(String, String)]  IO ByteString
curlResponse xs = withCurlDo $
  respBody <$> (curlGetResponse_ "http://ws.audioscrobbler.com/2.0/?"
    [ CurlPostFields . map (export . urlEncode) $ xs
    , CurlFailOnError False
    , CurlUserAgent "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 Iceweasel/10.0"
    ]  IO (CurlResponse_ [(String, String)] ByteString))

xml  [String]  Q [Dec]
xml = mapM func
  where func xs = funD (mkName xs) [clause [] (normalB $ appE (varE (mkName ("API." ++ xs))) [e| XML |]) []]

json  [String]  Q [Dec]
json = mapM func
  where func xs = funD (mkName xs) [clause [] (normalB $ appE (varE (mkName ("API." ++ xs))) [e| JSON |]) []]