{-|
Module      : Support
Description : Predicates and aggregates useful to relational querying
Copyright   : © Thor Michael Støre, 2015
License     : GPL v2 without "any later version" clause
Maintainer  : thormichael át gmâïl døt cöm
Stability   : experimental

Predicates and aggregates useful to relational querying. These are not part of
the relational algebra but are useful in relational queries in restriction
predicates, and extension and summarization expressions. It also has great
utility in non-relational queries, such as when performing aggregations.
-}

module Database.HaskRel.Support
       ( -- * Predicates
         (&=), plusminus, (+-), (±),
         between, (>=<=), (), xBetween, (><), (>=<), (<), (><=), (>),
         (), (), (),
         -- * Aggregates
         avg, minx, maxx )
       where

import Data.List (genericLength)


infix 5 &=

{-| Reverse predicate application. As "Data.Function.&", the reverse function
application operator, but restricted to a boolean result.
-}
(&=) :: a -> ( a -> Bool ) -> Bool
(&=) x f = f x

{-
-- TODO: Perhaps congruence (or a variation thereof?) is useful?
(≅) :: Eq a => t -> t -> (t -> a) -> Bool
(≅) a b f = f a == f b

>>> 38 ≅ 58 $ ( `mod` 10 )
True
-}


-- |>>> plusminus 9 3 10
--True
plusminus :: (Ord a, Num a) => a -> a -> a -> Bool
plusminus x y c = c >= ( x - y ) && c <= ( x + y )

infix 7 ±
infix 7 +-

{-| >>> 10 &= 9 ± 3
True
>>> rPrint$ p ∣ (\[pun|weight|] -> weight &= 9 ± 3)
┌─────┬───────┬───────┬────────┬────────┐
│ pno │ pName │ color │ weight │ city   │
╞═════╪═══════╪═══════╪════════╪════════╡
│ P1  │ Nut   │ Red   │ 12 % 1 │ London │
│ P5  │ Cam   │ Blue  │ 12 % 1 │ Paris  │
└─────┴───────┴───────┴────────┴────────┘
-}
(±) :: (Ord a, Num a) => a -> a -> a -> Bool
(±) = plusminus

-- |>>> 10 &= 9 +- 3
--True
(+-) :: (Ord a, Num a) => a -> a -> a -> Bool
(+-) = plusminus


-- |>>> 5 `between` (5,9)
--True
between :: Ord a => a -> (a, a) -> Bool
between c (l,h) = c >= l && c <= h


{-| >>> 5 &= 5 >=<= 9
True
-}
(>=<=) :: Ord a => a -> a -> a -> Bool
(>=<=) l h c = c >= l && c <= h

{-| >>> 5 &= 5 ≥≤ 9
True
>>> rPrint$ p ∣ (\[pun|weight|] -> weight &= 11 ≥≤ 14)
┌─────┬───────┬───────┬────────┬────────┐
│ pno │ pName │ color │ weight │ city   │
╞═════╪═══════╪═══════╪════════╪════════╡
│ P1  │ Nut   │ Red   │ 12 % 1 │ London │
│ P4  │ Screw │ Red   │ 14 % 1 │ London │
│ P5  │ Cam   │ Blue  │ 12 % 1 │ Paris  │
└─────┴───────┴───────┴────────┴────────┘
-}
() :: Ord a => a -> a -> a -> Bool
() l h c = c >= l && c <= h

{-|
Between exclusive

>>> 5 `xBetween` (5,9)
False
-}
xBetween :: Ord a => a -> (a, a) -> Bool
xBetween c (l,h) = c > l && c < h

{-| >>> 5 &= 5 >< 9
False
>>> rPrint$ p ∣ (\[pun|weight|] -> weight &= 11 >< 14)
┌─────┬───────┬───────┬────────┬────────┐
│ pno │ pName │ color │ weight │ city   │
╞═════╪═══════╪═══════╪════════╪════════╡
│ P1  │ Nut   │ Red   │ 12 % 1 │ London │
│ P5  │ Cam   │ Blue  │ 12 % 1 │ Paris  │
└─────┴───────┴───────┴────────┴────────┘
-}
(><) :: Ord a => a -> a -> a -> Bool
(><) l h c = c > l && c < h

-- |>>> 5 &= 5 >=< 9
--True
(>=<) :: Ord a => a -> a -> a -> Bool
(>=<) l h c = c >= l && c < h

-- |>>> 5 &= 5 ≥< 9
--True
(<) :: Ord a => a -> a -> a -> Bool
(<) l h c = c >= l && c < h

-- |>>> 5 &= 5 ><= 9
--False
(><=) :: Ord a => a -> a -> a -> Bool
(><=) l h c = c > l && c <= h

-- |>>> 5 &= 5 >≤ 9
--False
(>) :: Ord a => a -> a -> a -> Bool
(>) l h c = c > l && c <= h

-- | Synonym for <=
() :: Ord a => a -> a -> Bool
() x y = x <= y

-- | Synonym for >=
() :: Ord a => a -> a -> Bool
() x y = x >= y

-- | Synonym for /=
() :: Eq a => a -> a -> Bool
() x y = x /= y


{-| Average of a list of values

>>> let _qtys a = Label .=. a :: Tagged "qtys" Double
>>> pt$ group sp (rHdr (pno,qty)) (_qtys . avg . agg qty)
┌────────────────────┬───────────────┐
│ qtys :: Double     │ sno :: String │
╞════════════════════╪═══════════════╡
│ 200.0              │ S3            │
│ 216.66666666666666 │ S1            │
│ 300.0              │ S4            │
│ 350.0              │ S2            │
└────────────────────┴───────────────┘

Note the explicit type in the definition of @_qtys@; it is neccessary to provide
a specific type for 'pt' to format it as a table, even when an expression would
otherwise be correct without this. The following would blow up if prefixed with
"pt$":

>>> group sp (rHdr (pno,qty)) ((qtys .=.) . avg . agg qty)
fromList [Record{qtys=200.0,sno="S3"},Record{qtys=216.66666666666666,sno="S1"},Record{qtys=300.0,sno="S4"},Record{qtys=350.0,sno="S2"}]
-}
avg :: (Fractional a, Real a1) => [a1] -> a
avg xs = realToFrac (sum xs) / genericLength xs


{- | Minimum of several values, defaulting to the second argument if there are no
elements.

>>> pt$ group sp (rHdr (pno,qty)) ((qtys .=.) . (`minx` 0) . agg qty)
┌─────────────────┬───────────────┐
│ qtys :: Integer │ sno :: String │
╞═════════════════╪═══════════════╡
│ 100             │ S1            │
│ 200             │ S3            │
│ 200             │ S4            │
│ 300             │ S2            │
└─────────────────┴───────────────┘
-}
minx :: Ord a => [a] -> a -> a
minx [] d = d
minx xs _ = minimum xs

{- Or perhaps with Foldable:
minx :: (Ord a, Foldable t) => t a -> a -> a
minx fl d = if F.null fl then d
                         else F.minimum fl
-}

{- | Maximum of several values, defaulting to the second argument if there are no
elements

>>> pt$ group sp (rHdr (pno,qty)) ((qtys .=.) . (`maxx` 0) . agg qty)
┌─────────────────┬───────────────┐
│ qtys :: Integer │ sno :: String │
╞═════════════════╪═══════════════╡
│ 200             │ S3            │
│ 400             │ S1            │
│ 400             │ S2            │
│ 400             │ S4            │
└─────────────────┴───────────────┘
-}
maxx :: Ord a => [a] -> a -> a
maxx [] d = d
maxx xs _ = maximum xs