    {-# LANGUAGE CPP #-}

    {- |
    Module      : Network.MPD
    Copyright   : (c) Joachim Fasting, Simon Hengel 2012
    License     : MIT

    Maintainer  : Joachim Fasting <joachifm@fastmail.fm>
    Stability   : unstable
    Portability : unportable

    An MPD client library. MPD is a daemon for playing music that is
    controlled over a network socket.

    To use the library, do:

    > {-# LANGUAGE OverloadedStrings #-}
    > import qualified Network.MPD as MPD
    -}

    module Network.MPD (
        -- * Basic data types
        MonadMPD, MPD, MPDError(..), ACKType(..), Response,
        Host, Port, Password,
        -- * Connections
        withMPD, withMPD_, withMPDEx,
        module Network.MPD.Commands,
    #ifdef TEST
        getConnectionSettings, getEnvDefault
    #endif
        ) where

    import           Prelude
    import qualified Control.Exception as E
    import           Network.MPD.Commands
    import           Network.MPD.Core

    import           System.Environment (getEnv)
    import           System.IO.Error (isDoesNotExistError)
    import           Data.Maybe (listToMaybe)

    -- | A wrapper for 'withMPDEx' that uses localhost:6600 as the default
    -- host:port, or whatever is found in the environment variables MPD_HOST and
    -- MPD_PORT. If MPD_HOST is of the form \"password\@host\" the password
    -- will be supplied as well.
    --
    -- Examples:
    --
    -- > withMPD $ play Nothing
    -- > withMPD $ add_ "tool" >> play Nothing >> currentSong
    withMPD :: MPD a -> IO (Response a)
    withMPD :: forall a. MPD a -> IO (Response a)
    withMPD = Maybe String -> Maybe String -> MPD a -> IO (Response a)
    forall a. Maybe String -> Maybe String -> MPD a -> IO (Response a)
    withMPD_ Maybe String
    forall a. Maybe a
    Nothing Maybe String
    forall a. Maybe a
    Nothing

    -- | Same as `withMPD`, but takes optional arguments that override MPD_HOST and
    -- MPD_PORT.
    --
    -- This is e.g. useful for clients that optionally take @--port@ and @--host@
    -- as command line arguments, and fall back to `withMPD`'s defaults if those
    -- arguments are not given.
    withMPD_ :: Maybe String -- ^ optional override for MPD_HOST
             -> Maybe String -- ^ optional override for MPD_PORT
             -> MPD a -> IO (Response a)
    withMPD_ :: forall a. Maybe String -> Maybe String -> MPD a -> IO (Response a)
    withMPD_ Maybe String
    mHost Maybe String
    mPort MPD a
    action = do
        settings <- Maybe String
    -> Maybe String -> IO (Either String (String, Port, String))
    getConnectionSettings Maybe String
    mHost Maybe String
    mPort
        case settings of
          Right (String
    host, Port
    port, String
    pw) -> String -> Port -> String -> MPD a -> IO (Response a)
    forall a. String -> Port -> String -> MPD a -> IO (Response a)
    withMPDEx String
    host Port
    port String
    pw MPD a
    action
          Left String
    err -> (Response a -> IO (Response a)
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Response a -> IO (Response a))
    -> (String -> Response a) -> String -> IO (Response a)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MPDError -> Response a
    forall a b. a -> Either a b
    Left (MPDError -> Response a)
    -> (String -> MPDError) -> String -> Response a
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> MPDError
    Custom) String
    err

    getConnectionSettings :: Maybe String -> Maybe String -> IO (Either String (Host, Port, Password))
    getConnectionSettings :: Maybe String
    -> Maybe String -> IO (Either String (String, Port, String))
    getConnectionSettings Maybe String
    mHost Maybe String
    mPort = do
        (host, pw) <- String -> (String, String)
    parseHost (String -> (String, String)) -> IO String -> IO (String, String)
    forall a b. (a -> b) -> IO a -> IO b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    `fmap`
            IO String -> (String -> IO String) -> Maybe String -> IO String
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe (String -> String -> IO String
    getEnvDefault String
    "MPD_HOST" String
    "localhost") String -> IO String
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return Maybe String
    mHost
        port <- maybe (getEnvDefault "MPD_PORT" "6600") return mPort
        case maybeRead port of
          Just Port
    p  -> (Either String (String, Port, String)
    -> IO (Either String (String, Port, String))
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Either String (String, Port, String)
     -> IO (Either String (String, Port, String)))
    -> ((String, Port, String) -> Either String (String, Port, String))
    -> (String, Port, String)
    -> IO (Either String (String, Port, String))
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (String, Port, String) -> Either String (String, Port, String)
    forall a b. b -> Either a b
    Right) (String
    host, Port
    p, String
    pw)
          Maybe Port
    Nothing -> (Either String (String, Port, String)
    -> IO (Either String (String, Port, String))
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Either String (String, Port, String)
     -> IO (Either String (String, Port, String)))
    -> (String -> Either String (String, Port, String))
    -> String
    -> IO (Either String (String, Port, String))
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> Either String (String, Port, String)
    forall a b. a -> Either a b
    Left) (String -> String
    forall a. Show a => a -> String
    show String
    port String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    " is not a valid port!")
        where
            parseHost :: String -> (String, String)
    parseHost String
    s = case Char -> String -> (String, String)
    breakChar Char
    '@' String
    s of
                              (String
    host, String
    "") -> (String
    host, String
    "")
                              (String
    pw, String
    host) -> (String
    host, String
    pw)

    getEnvDefault :: String -> String -> IO String
    getEnvDefault :: String -> String -> IO String
    getEnvDefault String
    x String
    dflt =
        IO String -> (IOError -> IO String) -> IO String
    forall e a. Exception e => IO a -> (e -> IO a) -> IO a
    E.catch (String -> IO String
    getEnv String
    x) (\IOError
    e -> if IOError -> Bool
    isDoesNotExistError IOError
    e
                                then String -> IO String
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return String
    dflt else IOError -> IO String
    forall a. IOError -> IO a
    ioError IOError
    e)

    -- Break a string by character, removing the separator.
    breakChar :: Char -> String -> (String, String)
    breakChar :: Char -> String -> (String, String)
    breakChar Char
    c String
    s = let (String
    x, String
    y) = (Char -> Bool) -> String -> (String, String)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == Char
    c) String
    s in (String
    x, Int -> String -> String
    forall a. Int -> [a] -> [a]
    drop Int
    1 String
    y)

    maybeRead :: Read a => String -> Maybe a
    maybeRead :: forall a. Read a => String -> Maybe a
    maybeRead = ((a, String) -> a) -> Maybe (a, String) -> Maybe a
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (a, String) -> a
    forall a b. (a, b) -> a
    fst (Maybe (a, String) -> Maybe a)
    -> (String -> Maybe (a, String)) -> String -> Maybe a
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [(a, String)] -> Maybe (a, String)
    forall a. [a] -> Maybe a
    listToMaybe ([(a, String)] -> Maybe (a, String))
    -> (String -> [(a, String)]) -> String -> Maybe (a, String)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> [(a, String)]
    forall a. Read a => ReadS a
    reads