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

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

Query interface.
-}

module Network.MPD.Commands.Query (Query, (=?) ,(/=?), (%?), (~?), (/~?), qNot, qModSince, qFile, qBase, anything) where

import           Network.MPD.Commands.Arg
import           Network.MPD.Commands.Types
import           Data.Time (UTCTime,formatTime,defaultTimeLocale)

-- | An interface for creating MPD queries.
--
-- For example, to match any song where the value of artist is \"Foo\", we
-- use:
--
-- > Artist =? "Foo"
--
-- We can also compose queries, thus narrowing the search. For example, to
-- match any song where the value of artist is \"Foo\" and the value of album
-- is \"Bar\", we use:
--
-- > Artist =? "Foo" <> Album =? "Bar"
data Query = Query [Match] | Filter Expr
  deriving Int -> Query -> ShowS
[Query] -> ShowS
Query -> String
(Int -> Query -> ShowS)
-> (Query -> String) -> ([Query] -> ShowS) -> Show Query
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Query] -> ShowS
$cshowList :: [Query] -> ShowS
show :: Query -> String
$cshow :: Query -> String
showsPrec :: Int -> Query -> ShowS
$cshowsPrec :: Int -> Query -> ShowS
Show

-- A single query clause, comprising a metadata key and a desired value.
data Match = Match Metadata Value

instance Show Match where
  show :: Match -> String
show (Match Metadata
meta Value
query) = Metadata -> String
forall a. Show a => a -> String
show Metadata
meta String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" \"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. ToString a => a -> String
toString Value
query String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\""
  showList :: [Match] -> ShowS
showList [Match]
xs String
_ = [String] -> String
unwords ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (Match -> String) -> [Match] -> [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Match -> String
forall a. Show a => a -> String
show [Match]
xs

-- A general MPD 0.21 style filter.
data Expr = Exact Match
          | ExactNot Match
          | Contains Match
          | Regex Match
          | RegexNot Match
          | File Path
          | Base Path
          | ModifiedSince UTCTime
          | ExprNot Expr
          | ExprAnd Expr Expr
          | ExprEmpty
instance Show Expr where
 show :: Expr -> String
show (Exact (Match Metadata
meta Value
query)) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Metadata -> String
forall a. Show a => a -> String
show Metadata
meta String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" == " String -> ShowS
forall a. [a] -> [a] -> [a]
++
                                   String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. ToString a => a -> String
toString Value
query String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (ExactNot (Match Metadata
meta Value
query)) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Metadata -> String
forall a. Show a => a -> String
show Metadata
meta String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" != " String -> ShowS
forall a. [a] -> [a] -> [a]
++
                                   String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. ToString a => a -> String
toString Value
query String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (Contains (Match Metadata
meta Value
query)) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Metadata -> String
forall a. Show a => a -> String
show Metadata
meta String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" contains " String -> ShowS
forall a. [a] -> [a] -> [a]
++
                                   String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. ToString a => a -> String
toString Value
query String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (Regex (Match Metadata
meta Value
query)) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Metadata -> String
forall a. Show a => a -> String
show Metadata
meta String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" =~ " String -> ShowS
forall a. [a] -> [a] -> [a]
++
                                   String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. ToString a => a -> String
toString Value
query String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (RegexNot (Match Metadata
meta Value
query)) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Metadata -> String
forall a. Show a => a -> String
show Metadata
meta String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" !~ " String -> ShowS
forall a. [a] -> [a] -> [a]
++
                                   String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. ToString a => a -> String
toString Value
query String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (File Path
file) = String
"(file == " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Path -> String
forall a. ToString a => a -> String
toString Path
file String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (Base Path
dir) = String
"(base " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Path -> String
forall a. ToString a => a -> String
toString Path
dir String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (ModifiedSince UTCTime
time) = String
"(modified-since " String -> ShowS
forall a. [a] -> [a] -> [a]
++  String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++
                           TimeLocale -> String -> UTCTime -> String
forall t. FormatTime t => TimeLocale -> String -> t -> String
formatTime TimeLocale
defaultTimeLocale String
"%Y-%m-%dT%H:%M:%SZ" UTCTime
time String -> ShowS
forall a. [a] -> [a] -> [a]
++
                           String
"\\\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (ExprNot Expr
expr) = String
"(!" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expr -> String
forall a. Show a => a -> String
show Expr
expr String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show (ExprAnd Expr
e1 Expr
e2) = String
"(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expr -> String
forall a. Show a => a -> String
show Expr
e1 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" AND " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expr -> String
forall a. Show a => a -> String
show Expr
e2 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"
 show Expr
ExprEmpty = String
""

toExpr :: [Match] -> Expr
toExpr :: [Match] -> Expr
toExpr [] = Expr
ExprEmpty
toExpr (Match
m:[]) = Match -> Expr
Exact Match
m
toExpr (Match
m:[Match]
ms) = Expr -> Expr -> Expr
ExprAnd (Match -> Expr
Exact Match
m) ([Match] -> Expr
toExpr [Match]
ms)

instance Monoid Query where
    mempty :: Query
mempty  = [Match] -> Query
Query []
    Query  [Match]
a  mappend :: Query -> Query -> Query
`mappend` Query    [Match]
b  = [Match] -> Query
Query ([Match]
a [Match] -> [Match] -> [Match]
forall a. [a] -> [a] -> [a]
++ [Match]
b)
    Query  [] `mappend` Filter   Expr
b  = Expr -> Query
Filter Expr
b
    Filter Expr
a  `mappend` Query    [] = Expr -> Query
Filter Expr
a
    Query  [Match]
a  `mappend` Filter   Expr
b  = Expr -> Query
Filter (Expr -> Expr -> Expr
ExprAnd ([Match] -> Expr
toExpr [Match]
a) Expr
b)
    Filter Expr
a  `mappend` Query    [Match]
b  = Expr -> Query
Filter (Expr -> Expr -> Expr
ExprAnd Expr
a ([Match] -> Expr
toExpr [Match]
b))
    Filter Expr
a  `mappend` Filter   Expr
b  = Expr -> Query
Filter (Expr
a Expr -> Expr -> Expr
forall a. Semigroup a => a -> a -> a
<> Expr
b)

instance Semigroup Query where
    <> :: Query -> Query -> Query
(<>) = Query -> Query -> Query
forall a. Monoid a => a -> a -> a
mappend
instance Semigroup Expr where
  Expr
ex1 <> :: Expr -> Expr -> Expr
<> Expr
ex2 = Expr -> Expr -> Expr
ExprAnd Expr
ex1 Expr
ex2

instance MPDArg Query where
    prep :: Query -> Args
prep (Query [Match]
ms) = (Args -> Args -> Args) -> Args -> [Args] -> Args
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl Args -> Args -> Args
forall a b. (MPDArg a, MPDArg b) => a -> b -> Args
(<++>) ([String] -> Args
Args [])
                        ((Match -> Args) -> [Match] -> [Args]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(Match Metadata
m Value
q) -> [String] -> Args
Args [Metadata -> String
forall a. Show a => a -> String
show Metadata
m] Args -> Value -> Args
forall a b. (MPDArg a, MPDArg b) => a -> b -> Args
<++> Value
q) [Match]
ms)
    prep (Filter Expr
expr) = [String] -> Args
Args [String
"\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Expr -> String
forall a. Show a => a -> String
show Expr
expr String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\""]

-- | An empty query. Matches anything.
anything :: Query
anything :: Query
anything = Query
forall a. Monoid a => a
mempty

-- | Create a query matching a tag with a value.
(=?) :: Metadata -> Value -> Query
Metadata
m =? :: Metadata -> Value -> Query
=? Value
s = [Match] -> Query
Query [Metadata -> Value -> Match
Match Metadata
m Value
s]

-- | Create a query matching a tag with anything but a value.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
(/=?) :: Metadata -> Value -> Query
Metadata
m /=? :: Metadata -> Value -> Query
/=? Value
s = Expr -> Query
Filter (Match -> Expr
ExactNot (Metadata -> Value -> Match
Match Metadata
m Value
s))

-- | Create a query for a tag containing a value.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
(%?) :: Metadata -> Value -> Query
Metadata
m %? :: Metadata -> Value -> Query
%? Value
s = Expr -> Query
Filter (Match -> Expr
Contains (Metadata -> Value -> Match
Match Metadata
m Value
s))

-- | Create a query matching a tag with regexp.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
(~?) :: Metadata -> Value -> Query
Metadata
m ~? :: Metadata -> Value -> Query
~? Value
s = Expr -> Query
Filter (Match -> Expr
Regex (Metadata -> Value -> Match
Match Metadata
m Value
s))

-- | Create a query matching a tag with anything but a regexp.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
(/~?) :: Metadata -> Value -> Query
Metadata
m /~? :: Metadata -> Value -> Query
/~? Value
s = Expr -> Query
Filter (Match -> Expr
RegexNot (Metadata -> Value -> Match
Match Metadata
m Value
s))

-- | Negate a Query.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
qNot :: Query -> Query
qNot :: Query -> Query
qNot (Query [Match]
ms) = Expr -> Query
Filter (Expr -> Expr
ExprNot ([Match] -> Expr
toExpr [Match]
ms))
qNot (Filter (ExprNot Expr
ex)) = Expr -> Query
Filter Expr
ex
qNot (Filter Expr
ex) = Expr -> Query
Filter (Expr -> Expr
ExprNot Expr
ex)

-- | Create a query for songs modified since a date.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
qModSince :: UTCTime -> Query
qModSince :: UTCTime -> Query
qModSince UTCTime
time = Expr -> Query
Filter (UTCTime -> Expr
ModifiedSince UTCTime
time)

-- | Create a query for the full song URI relative to the music directory.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
qFile :: Path -> Query
qFile :: Path -> Query
qFile Path
file = Expr -> Query
Filter (Path -> Expr
File Path
file)

-- | Limit the query to the given directory, relative to the music directory.
--
-- Since MPD 0.21.
--
-- @since 0.9.3.0
qBase :: Path -> Query
qBase :: Path -> Query
qBase Path
dir = Expr -> Query
Filter (Path -> Expr
Base Path
dir)