module Network.MPD.Parse
  ( getStatusField
  , getStatusFieldElement
  , getTag
  , processSong
  , maybePathCurrentSong
  , maybePathNextPlaylistSong
  , headMay
  , valueToStringMay
  , (.=?)
  , objectJson
  , getStatusIdInt
  ) where

import qualified Network.MPD as MPD
import Network.MPD
       ( Metadata(..), Song, PlaybackState(Stopped, Playing, Paused) )
import Data.Aeson ( object, Key, KeyValue(..), ToJSON, Value )
import Data.Aeson.Types ( Pair )
import Data.Maybe ( catMaybes, fromMaybe )

{- | Extract a field from the returned MPD.Status data record.

Helper to extract a specific field from the
[Network.MPD.Status](Network.MPD#Status) data record by providing the
corresponding field label. If the input status "@st@" is /not/ @Right a@,
indicating an error, or the field label function is not applicable, it
returns @Nothing@.

==== __Example__:

@
ghci> import qualified Network.MPD as MPD
ghci> st <- MPD.withMPD MPD.status
ghci> getStatusField st MPD.stVolume
@
Just (Just 100)
-}
getStatusField :: MPD.Response MPD.Status -> (MPD.Status -> a) -> Maybe a
getStatusField :: forall a. Response Status -> (Status -> a) -> Maybe a
getStatusField (Right Status
st) Status -> a
f = a -> Maybe a
forall a. a -> Maybe a
Just (Status -> a
f Status
st)
getStatusField Response Status
_ Status -> a
_ = Maybe a
forall a. Maybe a
Nothing

{- | Go a level deeper than `getStatusField'. For nested @Maybe a@
fields from 'Network.MPD.Status'.

==== __Example__:

@
ghci> import qualified Network.MPD as MPD
ghci> st <- MPD.withMPD MPD.status
ghci> getStatusFieldElement st MPD.stVolume
@
Just 100
-}
getStatusFieldElement :: MPD.Response MPD.Status -> (MPD.Status -> Maybe a) -> Maybe a
getStatusFieldElement :: forall a. Response Status -> (Status -> Maybe a) -> Maybe a
getStatusFieldElement Response Status
status Status -> Maybe a
item = Maybe a -> Maybe (Maybe a) -> Maybe a
forall a. a -> Maybe a -> a
fromMaybe Maybe a
forall a. Maybe a
Nothing (Maybe (Maybe a) -> Maybe a) -> Maybe (Maybe a) -> Maybe a
forall a b. (a -> b) -> a -> b
$ Response Status -> (Status -> Maybe a) -> Maybe (Maybe a)
forall a. Response Status -> (Status -> a) -> Maybe a
getStatusField Response Status
status Status -> Maybe a
item

{- | @Either@ check for the returned value of 'Network.MPD.currentSong',
then call 'processSong' or return @Nothing@.
-}
getTag :: Metadata -> Either a (Maybe Song) -> Maybe String
getTag :: forall a. Metadata -> Either a (Maybe Song) -> Maybe String
getTag Metadata
t Either a (Maybe Song)
c =
  case Either a (Maybe Song)
c of
    Left a
_ -> Maybe String
forall a. Maybe a
Nothing
    Right Maybe Song
song -> Metadata -> Maybe Song -> Maybe String
processSong Metadata
t Maybe Song
song

{- | Use 'Network.MPD.sgGetTag' to extract a @tag@ from a @song@, safely
get only the head item of the returned @Maybe@ list, then safely
convert it to a string.
-}
processSong :: Metadata -> Maybe Song -> Maybe String
processSong :: Metadata -> Maybe Song -> Maybe String
processSong Metadata
_ Maybe Song
Nothing = Maybe String
forall a. Maybe a
Nothing
processSong Metadata
tag (Just Song
song) = do
  let tagVal :: Maybe [Value]
tagVal = Metadata -> Song -> Maybe [Value]
MPD.sgGetTag Metadata
tag Song
song
  Value -> Maybe String
valueToStringMay (Value -> Maybe String) -> Maybe Value -> Maybe String
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< ([Value] -> Maybe Value
forall a. [a] -> Maybe a
headMay ([Value] -> Maybe Value) -> Maybe [Value] -> Maybe Value
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Maybe [Value]
tagVal)

{- | Get the current 'Network.MPD.Song' relative path with 'Network.MPD.sgFilePath'
-}
maybePathCurrentSong :: MPD.Response (Maybe Song) -> Maybe String
maybePathCurrentSong :: Response (Maybe Song) -> Maybe String
maybePathCurrentSong Response (Maybe Song)
cs =
  case Response (Maybe Song)
cs of
    Left MPDError
_ -> Maybe String
forall a. Maybe a
Nothing
    Right Maybe Song
Nothing -> Maybe String
forall a. Maybe a
Nothing
    Right (Just Song
song) -> String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ Path -> String
forall a. ToString a => a -> String
MPD.toString (Path -> String) -> Path -> String
forall a b. (a -> b) -> a -> b
$ Song -> Path
MPD.sgFilePath Song
song

{- | Get the next song's relative path in the current playlist.

Using 'Network.MPD.sgFilePath' from the returned 'Network.MPD.Response' @[Song]@.
-}
maybePathNextPlaylistSong :: MPD.Response [Song] -> Maybe String
maybePathNextPlaylistSong :: Response [Song] -> Maybe String
maybePathNextPlaylistSong (Left MPDError
_)        = Maybe String
forall a. Maybe a
Nothing
maybePathNextPlaylistSong (Right [])      = Maybe String
forall a. Maybe a
Nothing
maybePathNextPlaylistSong (Right (Song
_:Song
_:[Song]
_)) = Maybe String
forall a. Maybe a
Nothing
maybePathNextPlaylistSong (Right [Song
s]) =  String -> Maybe String
forall a. a -> Maybe a
Just (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ Path -> String
forall a. ToString a => a -> String
MPD.toString (Path -> String) -> Path -> String
forall a b. (a -> b) -> a -> b
$ Song -> Path
MPD.sgFilePath Song
s

{- | Safely get the head of a list. Same as [Safe.headMay](Safe#headMay).
-}
headMay :: [a] -> Maybe a
headMay :: forall a. [a] -> Maybe a
headMay []    = Maybe a
forall a. Maybe a
Nothing
headMay (a
x:[a]
_) = a -> Maybe a
forall a. a -> Maybe a
Just a
x

{- | Convert 'Network.MPD.Value' to @String@ within a @Maybe@ context.

This @Value@ is from 'Network.MPD' and is basically the same as a
@String@ but used internally to store metadata values.

==== __Example__:

@
processSong :: Metadata -> Maybe Song -> Maybe String
processSong _ Nothing = Nothing
processSong tag (Just song) = do
  let tagVal = MPD.sgGetTag tag song
  valueToStringMay =<< (headMay =<< tagVal)
@

'MPD.sgGetTag' returns a @Maybe [Value]@. [libmpd](Network.MPD) also provides
'Network.MPD.toString' that can convert, along other types, a
'Network.MPD.Value' to a @String@.
-}
valueToStringMay :: MPD.Value -> Maybe String
valueToStringMay :: Value -> Maybe String
valueToStringMay Value
x = String -> Maybe String
forall a. a -> Maybe a
Just (Value -> String
forall a. ToString a => a -> String
MPD.toString Value
x)

{- | Check if @Maybe v@ exists and is of type expected by
'Data.Aeson.object' as defined in 'Data.Aeson.Value', if it is return
both the @key@ and @value@ within the @Maybe@ context tied with
'Data.Aeson..='. This gives support to \'optional\' fields using
'Data.Maybe.catMaybes' that discard @Nothing@ values and is meant to
prevent creating JSON key/value pairs with @null@ values, e.g.:

@
jsonTags = object . catMaybes $
    [ "artist"  .=? artist
    , "album"   .=? album
    , "title"   .=? title
    ]
@

Where if a value on the right is @Nothing@ that key/value pair will
not be included in 'Data.Aeson.object' because of
'Data.Maybe.catMaybes'.
-}
(.=?) :: (KeyValue e a, ToJSON v) => Key -> Maybe v -> Maybe a
Key
key .=? :: forall e a v. (KeyValue e a, ToJSON v) => Key -> Maybe v -> Maybe a
.=? Just v
value = a -> Maybe a
forall a. a -> Maybe a
Just (Key
key Key -> v -> a
forall v. ToJSON v => Key -> v -> a
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= v
value)
Key
_   .=? Maybe v
Nothing    = Maybe a
forall a. Maybe a
Nothing

-- | Helper function for creating an JSON 'Data.Aeson.object' where
-- 'Data.Maybe.catMaybes' won't include items from the '[Maybe Pair]'
-- list that return 'Nothing'.
objectJson :: [Maybe Pair] -> Value
objectJson :: [Maybe Pair] -> Value
objectJson = [Pair] -> Value
object ([Pair] -> Value)
-> ([Maybe Pair] -> [Pair]) -> [Maybe Pair] -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Maybe Pair] -> [Pair]
forall a. [Maybe a] -> [a]
catMaybes

-- | Extracts the 'Int' value from an 'MPD.Id' within 'MPD.Status', if
-- present and the 'Either' value is a 'Right'.
getStatusIdInt :: (MPD.Status -> Maybe MPD.Id) -> Either MPD.MPDError MPD.Status -> Maybe Int
getStatusIdInt :: (Status -> Maybe Id) -> Response Status -> Maybe Int
getStatusIdInt Status -> Maybe Id
item Response Status
status =
  case Maybe Id
m of
    Just (MPD.Id Int
int) -> Int -> Maybe Int
forall a. a -> Maybe a
Just Int
int
    Maybe Id
Nothing -> Maybe Int
forall a. Maybe a
Nothing
  where
    m :: Maybe Id
m = Maybe Id -> Maybe (Maybe Id) -> Maybe Id
forall a. a -> Maybe a -> a
fromMaybe Maybe Id
forall a. Maybe a
Nothing (Maybe (Maybe Id) -> Maybe Id) -> Maybe (Maybe Id) -> Maybe Id
forall a b. (a -> b) -> a -> b
$ Response Status -> (Status -> Maybe Id) -> Maybe (Maybe Id)
forall a. Response Status -> (Status -> a) -> Maybe a
getStatusField Response Status
status Status -> Maybe Id
item