{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Network.MPD.Applicative.Database
Copyright   : (c) Joachim Fasting 2012
License     : MIT

Maintainer  : joachifm@fastmail.fm
Stability   : stable
Portability : unportable

The music database.
-}

module Network.MPD.Applicative.Database where

import           Control.Applicative

import qualified Network.MPD.Commands.Arg as Arg
import           Network.MPD.Commands.Arg hiding (Command)
import           Network.MPD.Commands.Parse
import           Network.MPD.Commands.Query
import           Network.MPD.Util
import           Network.MPD.Commands.Types
import           Network.MPD.Applicative.Internal
import           Network.MPD.Applicative.Util

-- | Get a count of songs and their total playtime that exactly match the
-- query.
count :: Query -> Command Count
count q = Command (liftParser parseCount) ["count" <@> q]

-- | Find songs matching the query exactly.
find :: Query -> Command [Song]
find q = Command p ["find" <@> q]
    where
        p :: Parser [Song]
        p = liftParser takeSongs

-- | Like 'find' but adds the results to the current playlist.
findAdd :: Query -> Command ()
findAdd q = Command emptyResponse ["findadd" <@> q]

-- | Lists all tags of the specified type.
--
-- Note that the optional artist value is only ever used if the
-- metadata type is 'Album', and is then taken to mean that the albums
-- by that artist be listed.
list :: Metadata -> Maybe Artist -> Command [Value]
list m q = Command p c
    where
        p = map Value . takeValues <$> getResponse
        c = case m of
                Album -> ["list Album" <@> q]
                _     -> ["list" <@> m]

-- | List all songs and directories in a database path.
listAll :: Path -> Command [Path]
listAll path = Command p ["listall" <@> path]
    where
        p :: Parser [Path]
        p = map (Path . snd) . filter ((== "file") . fst)
            . toAssocList <$> getResponse

-- Internal helper
lsInfo' :: Arg.Command -> Path -> Command [LsResult]
lsInfo' cmd path = Command p [cmd <@> path]
    where
        p :: Parser [LsResult]
        p = liftParser takeEntries

-- | Same as 'listAll' but also returns metadata.
listAllInfo :: Path -> Command [LsResult]
listAllInfo = lsInfo' "listallinfo"

-- | List the contents of a database directory.
lsInfo :: Path -> Command [LsResult]
lsInfo = lsInfo' "lsinfo"

-- | Read comments from the file at the specified path.
readComments :: Path -> Command [(String, String)]
readComments uri = Command p ["readcomments" <@> uri]
  where p = map decodePair . toAssocList <$> getResponse

-- | Like 'find' but with inexact matching.
search :: Query -> Command [Song]
search q = Command p ["search" <@> q]
    where
        p :: Parser [Song]
        p = liftParser takeSongs

-- | Like 'search' but adds the results to the current playlist.
--
-- Since MPD 0.17.
searchAdd :: Query -> Command ()
searchAdd q = Command emptyResponse ["searchadd" <@> q]

-- | Like 'searchAdd' but adds results to the named playlist.
--
-- Since MPD 0.17.
searchAddPl :: PlaylistName -> Query -> Command ()
searchAddPl pl q = Command emptyResponse ["searchaddpl" <@> pl <++> q]

-- | Update the music database.
-- If no path is supplied, the entire database is updated.
update :: Maybe Path -> Command Integer
update = update_ "update"

-- | Like 'update' but also rescan unmodified files.
rescan :: Maybe Path -> Command Integer
rescan = update_ "rescan"

-- A helper for 'update' and 'rescan.
update_ :: Arg.Command -> Maybe Path -> Command Integer
update_ cmd mPath = Command p [cmd <@> mPath]
    where
        p :: Parser Integer
        p = do
            r <- getResponse
            case toAssocList r of
                [("updating_db", id_)] -> maybe (unexpected r)
                                                return
                                                (parseNum id_)
                _                      -> unexpected r