-- | AUR json API implementation (v5)
--
-- AUR json API spec can be found at https://wiki.archlinux.org/index.php/AurJson
--
{-# LANGUAGE OverloadedStrings #-}
module Distribution.ArchLinux.AUR.RPC
    ( info
    , searchBy
    , search
    ) where

import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text as Text
import           Data.Text (Text)
import           Control.Monad.Except
import           Control.Monad.Catch
import           Network.HTTP.Client
import           Network.HTTP.Client.TLS
import           Data.Aeson
import           Distribution.ArchLinux.AUR.Types

aurServer = "https://aur.archlinux.org/rpc/?v=" ++ show aurApi
  where aurApi = 5

genURL :: AURQuery -> String
genURL (QSearch field txt) = aurServer ++ "&type=search&by=" ++ show field ++ "&arg=" ++ txt
genURL (QInfo q) = aurServer ++ "&type=info" ++ concatMap (\x -> "&arg%5b%5d="++x) q

get s = join $ liftM2 httpLbs (parseUrl (genURL s)) (newManager tlsManagerSettings)

getM :: (MonadIO m) => AURQuery -> ExceptT String m (Response LBS.ByteString)
getM s = ExceptT . liftIO $ fmap Right (get s) `catch` (\(SomeException e) -> return (Left (show e)))

queryAUR :: (MonadIO m) => AURQuery -> ExceptT String m [AURInfo]
queryAUR s = getM s >>= \r -> ExceptT . return $ (eitherDecode >=> getAURInfo) (responseBody r)

concatMapM :: (Monad m, Foldable t) => (a -> m [b]) -> t a -> m [b]
concatMapM op = foldr f (return [])
    where
      f x xs = do x <- op x
                  if null x then xs else fmap (x ++) xs

-- |Query info of given list of packages, match exact names
-- possible return types are /multiinfo/ and /error/.
-- /error/ type is captured by ExceptT (Left).
-- However, query may return empty list which isn't considered as an error.
info :: (MonadIO m) => [String] -> ExceptT String m [AURInfo]
info = concatMapM (queryAUR . QInfo) . chunks

-- Avoid encode arbitrary long URL when ``info`` package list is too long.
chunks :: [String] -> [[String]]
chunks s = if null s then [] else s1 : chunks s'
  where (s1, s') = splitAt 1024 s

-- |searchBy field 'SearchBy' given string on AUR server
-- possible return types are /search/ and /error/.
-- Like 'info', /error/ is captured by a Left.
searchBy :: (MonadIO m) => SearchBy -> String -> ExceptT String m [AURInfo]
searchBy f = queryAUR . QSearch f

-- |synonym of 'searchBy' /ByNameDesc/
search :: (MonadIO m) => String -> ExceptT String m [AURInfo]
search = searchBy ByNameDesc

url1 = "https://aur.archlinux.org/rpc/?v=5&type=info&arg%5b%5d=icaclient"