{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.Plan.Many
  ( Many
  , NotAKey (NotAKey)
  , fromKeys
  , lookup
  , keys
  , elems
  , map
  , toMap
  , apply
  , compose
  )
where

import Prelude (Either (Left, Right), Functor (fmap), Maybe (Just, Nothing), Ord, ($), (.), (<*>))

import qualified Data.Either as Either
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe

{- |
  'NotAKey' is returned from various 'Many' related functions when presented
  with an input parameter that was not one of the original inputs that the
  'Many' was constructed with.

@since 1.0.0.0
-}
data NotAKey
  = NotAKey

{- |
  A 'Many k a' represents a group of values keyed by list of parameters and
  is used to return the results of executing an Orville Plan with a list of
  input parameters. If you need to find the result of the query associated
  with a particular input parameter, you can use 'lookup' to find it. If you
  don't care about the association with particular inputs, you can simply
  use 'elems' to get a list of all the results.

@since 1.0.0.0
-}
data Many k a
  = Many [k] (k -> Either NotAKey a)

instance Functor (Many k) where
  fmap :: forall a b. (a -> b) -> Many k a -> Many k b
fmap = (a -> b) -> Many k a -> Many k b
forall a b k. (a -> b) -> Many k a -> Many k b
map

{- |
  'fromKeys' constructs a 'Many' value from a list of keys and a function that
  maps them to their values. The order and duplication of keys in the list will
  be preserved by the 'Many' type in the relevant functions. The mapping
  function provided should be a total function -- i.e. it should not produce a
  runtime error. If it is not possible to map every @k@ (even those not in the
  input list provided to 'fromKeys'), the values should be wrapped in an
  appropriate type such as 'Maybe' so that an empty or default value can be
  returned.

@since 1.0.0.0
-}
fromKeys :: [k] -> (k -> Either NotAKey a) -> Many k a
fromKeys :: forall k a. [k] -> (k -> Either NotAKey a) -> Many k a
fromKeys =
  [k] -> (k -> Either NotAKey a) -> Many k a
forall k a. [k] -> (k -> Either NotAKey a) -> Many k a
Many

{- |
   'map' calls a function on all the values found in a 'Many' collection.

@since 1.0.0.0
-}
map :: (a -> b) -> Many k a -> Many k b
map :: forall a b k. (a -> b) -> Many k a -> Many k b
map a -> b
f (Many [k]
ks k -> Either NotAKey a
keyToValue) =
  [k] -> (k -> Either NotAKey b) -> Many k b
forall k a. [k] -> (k -> Either NotAKey a) -> Many k a
Many [k]
ks ((a -> b) -> Either NotAKey a -> Either NotAKey b
forall a b. (a -> b) -> Either NotAKey a -> Either NotAKey b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> b
f (Either NotAKey a -> Either NotAKey b)
-> (k -> Either NotAKey a) -> k -> Either NotAKey b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. k -> Either NotAKey a
keyToValue)

{- |
   'apply' allows you to apply many functions to many values. The function
   associated with each parameter is applied to the value associated with the
   same paremeter.

   (If you're looking for 'Prelude.pure' or an 'Prelude.Applicative' instance
   for 'Many', this is as good as it gets. 'Many' cannot be an
   'Prelude.Applicative' because there is no correct implementation of
   'Prelude.pure' that we can reasonably provide).

@since 1.0.0.0
-}
apply ::
  (Many param (a -> b)) ->
  (Many param a) ->
  (Many param b)
apply :: forall param a b.
Many param (a -> b) -> Many param a -> Many param b
apply Many param (a -> b)
manyFs Many param a
manyAs =
  [param] -> (param -> Either NotAKey b) -> Many param b
forall k a. [k] -> (k -> Either NotAKey a) -> Many k a
fromKeys (Many param (a -> b) -> [param]
forall k a. Many k a -> [k]
keys Many param (a -> b)
manyFs) param -> Either NotAKey b
applyF
 where
  applyF :: param -> Either NotAKey b
applyF param
param =
    param -> Many param (a -> b) -> Either NotAKey (a -> b)
forall k a. k -> Many k a -> Either NotAKey a
lookup param
param Many param (a -> b)
manyFs Either NotAKey (a -> b) -> Either NotAKey a -> Either NotAKey b
forall a b.
Either NotAKey (a -> b) -> Either NotAKey a -> Either NotAKey b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> param -> Many param a -> Either NotAKey a
forall k a. k -> Many k a -> Either NotAKey a
lookup param
param Many param a
manyAs

{- |
  'compose' uses the values of a 'Many' value as keys to a second 'Many' to
  create a 'Many' mapping from the original keys to the final values.

@since 1.0.0.0
-}
compose :: Many b c -> Many a b -> Many a c
compose :: forall b c a. Many b c -> Many a b -> Many a c
compose Many b c
manyBC Many a b
manyAB =
  [a] -> (a -> Either NotAKey c) -> Many a c
forall k a. [k] -> (k -> Either NotAKey a) -> Many k a
fromKeys (Many a b -> [a]
forall k a. Many k a -> [k]
keys Many a b
manyAB) a -> Either NotAKey c
aToC
 where
  aToC :: a -> Either NotAKey c
aToC a
a = do
    b
b <- a -> Many a b -> Either NotAKey b
forall k a. k -> Many k a -> Either NotAKey a
lookup a
a Many a b
manyAB
    b -> Many b c -> Either NotAKey c
forall k a. k -> Many k a -> Either NotAKey a
lookup b
b Many b c
manyBC

{- |
  'keys' fetches the list of keys from a 'Many'. Note that is a list and not
  a set. 'Many' preserves the order and duplication of any key values that were
  in the key list at the time of construction.

@since 1.0.0.0
-}
keys :: Many k a -> [k]
keys :: forall k a. Many k a -> [k]
keys (Many [k]
ks k -> Either NotAKey a
_) =
  [k]
ks

{- |
  'elems' returns all the values that correspond to the keys of the 'Many'. The
  values will be returned in the same order that the keys were present at the
  time of creation, though if you truly care about this it's probably better to
  use 'lookup' to make that correspondence explicit.

@since 1.0.0.0
-}
elems :: Many k a -> [a]
elems :: forall k a. Many k a -> [a]
elems (Many [k]
ks k -> Either NotAKey a
keyToValue) =
  [Either NotAKey a] -> [a]
forall a b. [Either a b] -> [b]
Either.rights ([Either NotAKey a] -> [a]) -> [Either NotAKey a] -> [a]
forall a b. (a -> b) -> a -> b
$ (k -> Either NotAKey a) -> [k] -> [Either NotAKey a]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap k -> Either NotAKey a
keyToValue [k]
ks

{- |
  'toMap' converts the 'Many' into a 'Map.Map' value. If all you wanted to do
  was find the value for a specific key, you should probably use 'lookup'
  instead.

@since 1.0.0.0
-}
toMap :: Ord k => Many k a -> Map.Map k a
toMap :: forall k a. Ord k => Many k a -> Map k a
toMap (Many [k]
ks k -> Either NotAKey a
keyToValue) =
  [(k, a)] -> Map k a
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ((k -> Maybe (k, a)) -> [k] -> [(k, a)]
forall a b. (a -> Maybe b) -> [a] -> [b]
Maybe.mapMaybe k -> Maybe (k, a)
mkPair [k]
ks)
 where
  mkPair :: k -> Maybe (k, a)
mkPair k
k =
    case k -> Either NotAKey a
keyToValue k
k of
      Left NotAKey
NotAKey ->
        Maybe (k, a)
forall a. Maybe a
Nothing
      Right a
value ->
        (k, a) -> Maybe (k, a)
forall a. a -> Maybe a
Just (k
k, a
value)

{- |
  'lookup' returns the value for the given parameter. If the given @k@ is
  not one of the original input values that the 'Many' was constructed with,
  the mapping function given at the contructor will determine what value to
  return. Often this will be whatever a reasonable empty or default value for
  the type @a@ is.

@since 1.0.0.0
-}
lookup :: k -> Many k a -> Either NotAKey a
lookup :: forall k a. k -> Many k a -> Either NotAKey a
lookup k
k (Many [k]
_ k -> Either NotAKey a
keyToValue) =
  k -> Either NotAKey a
keyToValue k
k