{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE QuasiQuotes          #-}
{-# LANGUAGE TemplateHaskell      #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Database.Bolt.Extras.Internal.Cypher
  (
    ToCypher (..)
  ) where

-------------------------------------------------------------------------------------------------
-- Queries for neo4j are formatted with `Cypher` language.
-- Read documentation for `Cypher` here: https://neo4j.com/docs/developer-manual/current/cypher/.
-- This file contains some converation rules from 'Database.Bolt' types to `Cypher`.
-------------------------------------------------------------------------------------------------

import           Data.Text                           as T (Text, concat, cons,
                                                           intercalate, pack,
                                                           replace, toUpper)
import           Database.Bolt                       (Value (..))
import           Database.Bolt.Extras.Internal.Types (Label, Property)
import           Database.Bolt.Extras.Utils          (currentLoc)
import           NeatInterpolation                   (text)

-- | The class for convertation into Cypher.
--
class ToCypher a where
  toCypher :: a -> Text

-- | Convertation for 'Database.Bolt.Value' into Cypher.
--
instance ToCypher Value where
  toCypher :: Value -> Text
toCypher (N ())     = Text
""
  toCypher (B Bool
bool)   = Text -> Text
toUpper (Text -> Text) -> (Bool -> Text) -> Bool -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
pack (String -> Text) -> (Bool -> String) -> Bool -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> String
forall a. Show a => a -> String
show (Bool -> Text) -> Bool -> Text
forall a b. (a -> b) -> a -> b
$ Bool
bool
  toCypher (I Int
int)    = String -> Text
pack (String -> Text) -> (Int -> String) -> Int -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String
forall a. Show a => a -> String
show (Int -> Text) -> Int -> Text
forall a b. (a -> b) -> a -> b
$ Int
int
  toCypher (F Double
double) = String -> Text
pack (String -> Text) -> (Double -> String) -> Double -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Double -> String
forall a. Show a => a -> String
show (Double -> Text) -> Double -> Text
forall a b. (a -> b) -> a -> b
$ Double
double
  toCypher (T Text
t)      = Text
"\"" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
escapeSpecSymbols Text
t Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\""
  toCypher (L [Value]
values) = let cvalues :: Text
cvalues = Text -> [Text] -> Text
T.intercalate Text
"," ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (Value -> Text) -> [Value] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Value -> Text
forall a. ToCypher a => a -> Text
toCypher [Value]
values
                        in [text|[$cvalues]|]
  toCypher Value
_          = String -> Text
forall a. HasCallStack => String -> a
error (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ String
$currentLoc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"unacceptable Value type"

escapeSpecSymbols :: Text -> Text
escapeSpecSymbols :: Text -> Text
escapeSpecSymbols = Text -> Text -> Text -> Text
replace Text
"\"" Text
"\\\"" (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Text -> Text
replace Text
"\\" Text
"\\\\"

-- | Label with @name@ are formatted into @:name@
--
instance ToCypher Label where
  toCypher :: Text -> Text
toCypher = Char -> Text -> Text
cons Char
':'

-- | Several labels are formatted with concatenation.
--
instance ToCypher [Label] where
  toCypher :: [Text] -> Text
toCypher = [Text] -> Text
T.concat ([Text] -> Text) -> ([Text] -> [Text]) -> [Text] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
forall a. ToCypher a => a -> Text
toCypher

-- | Converts property with @name@ and @value@ to @name:value@.
--
instance ToCypher Property where
  toCypher :: Property -> Text
toCypher (Text
propTitle, Value
value) = [Text] -> Text
T.concat [Text
propTitle, String -> Text
pack String
":", Value -> Text
forall a. ToCypher a => a -> Text
toCypher Value
value]

instance ToCypher (Text, Text) where
  toCypher :: (Text, Text) -> Text
toCypher (Text
propTitle, Text
param) = Text
propTitle Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":$" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
param

-- | Several properties are formatted with concatenation.
--
instance ToCypher [Property] where
  toCypher :: [Property] -> Text
toCypher = Text -> [Text] -> Text
T.intercalate Text
"," ([Text] -> Text) -> ([Property] -> [Text]) -> [Property] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Property -> Text) -> [Property] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Property -> Text
forall a. ToCypher a => a -> Text
toCypher

instance ToCypher [(Text, Text)] where
  toCypher :: [(Text, Text)] -> Text
toCypher = Text -> [Text] -> Text
T.intercalate Text
"," ([Text] -> Text)
-> ([(Text, Text)] -> [Text]) -> [(Text, Text)] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Text) -> Text) -> [(Text, Text)] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text, Text) -> Text
forall a. ToCypher a => a -> Text
toCypher