{-# LANGUAGE    TypeSynonymInstances
              , OverloadedStrings
              , MultiParamTypeClasses
              , FlexibleInstances #-}
{-|
Module      : TripLL.Bindings.LevelDBHexa
Description : An implementation for the tripLL interface.
Copyright   : (c) Philipp Pfeiffer, 2015
License     : MIT
Maintainer  : pfiff@hax-f.net
Stability   : experimental
Portability : POSIX

Gives an implementation for the tripLL interface based upon the `LevelDB` database.
-}
module TripLL.Bindings.LevelDBHexa (

  -- * Base Types
  LevelDBHexa (..),
  -- Re-Exports:
  module TripLL
) where

---- LevelDB: ----
import qualified Database.LevelDB.Base      as LDB
import qualified Database.LevelDB.Streaming as S
import qualified Database.LevelDB.Internal  as LDBI
---- Intern: ----
import TripLL
---- Data: ----
import qualified Data.ByteString      as BS
import qualified Data.ByteString.Lazy as LS
import qualified Data.Serialize       as BIN
import Data.Either (rights)
---- Control: ----
import Control.Applicative
import Control.Monad



-------------
-- Base Types
-------------
-- | The main handle of this implementation, 'LevelDBHexa'.
type LevelDBHexa = LDB.DB
type ByTriple = Triple BS.ByteString
type QueryByTriple = QueryTriple BS.ByteString


-------------------
-- Encoding:
-------------------
instance BIN.Serialize ByTriple where
  put (Triple s p o) = BIN.put (s,p,o)
  get = fmap (\(s,p,o) -> Triple s p o) BIN.get


----------------
-- Intern Misc
----------------
tupleKey :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString
tupleKey i v1 v2 v3 = i
           `BS.append` "::"
           `BS.append` v1
           `BS.append` "::"
           `BS.append` v2
           `BS.append` "::"
           `BS.append` v3


queryOpener :: Maybe BS.ByteString -> Maybe BS.ByteString -> Maybe BS.ByteString -> BS.ByteString
queryOpener Nothing Nothing Nothing    = "spo::"
queryOpener (Just s) Nothing Nothing   = "spo::" `BS.append` s
queryOpener Nothing (Just p) Nothing   = "pso::" `BS.append` p
queryOpener Nothing Nothing (Just o)   = "ops::" `BS.append` o
queryOpener (Just s) (Just p) Nothing  = "spo::" `BS.append` s `BS.append` "::" `BS.append` p
queryOpener (Just s) Nothing (Just o)  = "osp::" `BS.append` o `BS.append` "::" `BS.append` s
queryOpener Nothing (Just p) (Just o)  = "ops::" `BS.append` o `BS.append` "::" `BS.append` p
queryOpener (Just s) (Just p) (Just o) = "spo::" `BS.append` s `BS.append` "::" `BS.append` p `BS.append` "::" `BS.append` o


---------------------
-- Hexastore Instance
---------------------
instance Triplestore LevelDBHexa BS.ByteString where
  -------------------
  -- PUT into
  -------------------
  put (Triple s p o) db = let
                                encodedTrip = BIN.encode (Triple s p o)
                           in
                                LDB.write db LDB.defaultWriteOptions
                                  [ LDB.Put (tupleKey "spo" s p o) encodedTrip
                                  --, LDB.Put (tupleKey "sop" s o p) encodedTrip
                                  , LDB.Put (tupleKey "pso" p s o) encodedTrip
                                  --, LDB.Put (tupleKey "pos" p o s) encodedTrip
                                  , LDB.Put (tupleKey "osp" o s p) encodedTrip
                                  , LDB.Put (tupleKey "ops" o p s) encodedTrip ]

  -------------------
  -- DELETE
  -------------------
  del (Triple s p o) db = LDB.write db LDB.defaultWriteOptions
                                 [ LDB.Del (tupleKey "spo" s p o)
                                 --, LDB.Del (tupleKey "sop" s o p)
                                 , LDB.Del (tupleKey "pso" p s o)
                                 --, LDB.Del (tupleKey "pos" p o s)
                                 , LDB.Del (tupleKey "osp" o s p)
                                 , LDB.Del (tupleKey "ops" o p s) ]

  -----------------------
  -- BATCH operations
  -----------------------
  batch [] _ = return ()
  batch (Put t:xs) db = do { TripLL.put t db; batch xs db }
  batch (Delete t:xs) db = do { TripLL.del t db; batch xs db }

  ----------------------
  -- QUERY
  ----------------------
  query (Triple s p o) db  =
    let
        op = queryOpener s p o
    in
      LDB.withIter db LDB.defaultReadOptions $ \iter ->
        do
          entries <- S.toList $ S.entrySlice iter
                        (S.KeyRange
                            op
                            (\bs -> compare bs (op `BS.append` "\xff")))
                        S.Asc
          return $ rights (fmap (BIN.decode . snd) entries)

  --------------------------
  -- WITHHEXA bracket
  --------------------------
  withTrip path = LDB.withDB path (LDB.defaultOptions { LDB.createIfMissing = False, LDB.errorIfExists = False })

  -------------------------
  -- CREATE
  -------------------------
  createAndOpen path = LDB.open path (LDB.defaultOptions { LDB.createIfMissing = True, LDB.errorIfExists = True })
  open path = LDB.open path (LDB.defaultOptions { LDB.createIfMissing = False, LDB.errorIfExists = False})
  close = LDBI.unsafeClose