{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{- This example shows how to extract all
 - user friends, get their similarity score and
 - then sort them based on it outputting top 5
 -
 - User friends of which are searched is 'target'
 -
 - Most of hard work there is on 'aeson-lens' actually :)
 -
 - Sample output:
 -
 - % ./examples/sort-friends.hs
 - Artvart: 0.9969767332077
 - smpcln: 0.9965825676918
 - nv0id: 0.86804795265198
 - QueenrXy: 0.5932799577713
 - GhostOsaka: 0.2875879406929
 -
 - Notice: you may want to adjust maximum open files limit
 - if testing on users with relatively large friends count
 -}
import           Control.Concurrent.Async                  -- async
import           Control.Lens                              -- lens
import           Data.Aeson.Lens                           -- lens
import           Data.Aeson (Value)                        -- aeson
import           Data.Function (on)                        -- base
import           Data.List (sortBy)                        -- base
import           Data.Maybe (fromMaybe)                    -- base
import           Data.Monoid ((<>))                        -- base
import           Data.Text (Text)                          -- text
import           Data.Text.Lens (unpacked)                 -- lens
import qualified Data.Text.IO as Text                      -- text
import           Network.Lastfm hiding (to)                -- liblastfm
import qualified Network.Lastfm.User as User               -- liblastfm
import qualified Network.Lastfm.Tasteometer as Tasteometer -- liblastfm
import           Text.Read (readMaybe)                     -- base


type Score = Text


main :: IO ()
main = pages >>= scores . names >>= pretty
 where
  names x = x ^.. folded.folded.key "friends".key "user"._Array.folded.key "name"._String

pages :: IO [Either LastfmError Value]
pages = do
  first <- query (User.getFriends <*> user target)
  let ps = fromMaybe 1 (preview total first)
  (first :) <$> forConcurrently [2..ps] (\p -> query (User.getFriends <*> user target <* page p))
 where
  total = folded.key "friends".key "@attr".key "totalPages"._String.unpacked.to readMaybe.folded

scores :: [Text] -> IO [(Text, Score)]
scores xs = zip xs <$> forConcurrently xs (\x -> do
  r <- query (Tasteometer.compare (user target) (user x))
  return (fromMaybe "0" (preview score r)))
 where
  score = folded.key "comparison".key "result".key "score"._String

forConcurrently :: [a] -> (a -> IO b) -> IO [b]
forConcurrently = flip mapConcurrently

pretty :: [(Text, Score)] -> IO ()
pretty = mapM_ (\(n,s) -> Text.putStrLn $ n <> ": " <> s) . take 5 . sortBy (flip compare `on` snd)

query :: Request JSON (APIKey -> Ready) -> IO (Either LastfmError Value)
query r = lastfm (r <*> apiKey "234fc6e0f41f6ef99b7bd62ebaf8d318" <* json)

target :: Text
target = "MCDOOMDESTROYER"
