{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}


{-|

This module contains RethinkDB Expressions which are used to build queries.

-}

module Avers.Storage.Expressions where


import qualified Data.Vector        as V

import           Data.Text (Text)
import           Database.RethinkDB as R

import           Avers.Types hiding (Object)

import           Prelude hiding (lookup)



-- All the tables which this storage engine uses.
objectsTable, sessionsTable, snapshotsTable, patchesTable, secretsTable, blobsTable :: Exp Table
blobsTable     = Table Nothing $ lift ("blobs" :: Text)
objectsTable   = Table Nothing $ lift ("objects" :: Text)
patchesTable   = Table Nothing $ lift ("patches" :: Text)
secretsTable   = Table Nothing $ lift ("secrets" :: Text)
sessionsTable  = Table Nothing $ lift ("sessions" :: Text)
snapshotsTable = Table Nothing $ lift ("snapshots" :: Text)


-- | The primary key in all our documents is the default "id".
primaryKeyField :: Text
primaryKeyField = "id"


-- | Expression which represents the primary key field.
primaryKeyFieldE :: Exp Text
primaryKeyFieldE = lift primaryKeyField


-- | Expression which represents the value of a field inside of an Object.
objectFieldE :: (IsDatum a) => Text -> Exp Object -> Exp a
objectFieldE field obj = GetField (lift field) obj


-- | True if the object field matches the given value.
objectFieldEqE :: (ToDatum a) => Text -> a -> Exp Object -> Exp Bool
objectFieldEqE field value obj = Eq
    (objectFieldE field obj :: Exp Datum)
    (lift $ toDatum value)


-- | True if the object's primary key matches the given string.
primaryKeyEqE :: Text -> Exp Object -> Exp Bool
primaryKeyEqE = objectFieldEqE primaryKeyField


-- | Take the first item out of a sequence. Beware that this throws an error
-- when the sequence is empty.
headE :: (IsSequence a, IsDatum r) => Exp a -> Exp r
headE = Nth 0


-- | Limit a sequence to the first 'n' items.
limitE :: (IsSequence s) => Int -> Exp s -> Exp s
limitE n s = Limit (fromIntegral n) s


mkBounds :: ObjectId -> Int -> Int -> (Bound, Bound)
mkBounds objId lo hi = (mkBound objId lo, mkBound objId hi)

mkBound :: ObjectId -> Int -> Bound
mkBound objId revId = Closed $ Array $ V.fromList
    [String $ toPk objId, Number $ fromIntegral revId]


objectSnapshotSequenceE :: ObjectId -> Int -> Int -> Exp Table
objectSnapshotSequenceE objId lo hi =
    BetweenIndexed "objectSnapshotSequence" (mkBounds objId lo hi) $
    snapshotsTable


objectPatchSequenceE :: ObjectId -> Int -> Int -> Exp Table
objectPatchSequenceE objId lo hi =
    BetweenIndexed "objectPatchSequence" (mkBounds objId lo hi) $
    patchesTable