{-# LANGUAGE DeriveFunctor        #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE RecordWildCards      #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Database.Bolt.Extras.DSL.Internal.Types
  (
    NodeSelector (..)
  , RelSelector (..)
  , PathPart (..)
  , PathSelector (..)
  , Selector (..)
  , Selectors
  , Cond (..)
  , Conds (..)
  , Expr (..)
  , SelectorLike (..)
  , (.:)
  , (.#)
  , (#)
  , (-:)
  , (<-:)
  , defaultNode
  , defN
  , defaultRel
  , defR
  , toNodeSelector
  , toRelSelector
  ) where

import           Data.Foldable        (foldl')
import           Data.Map.Strict      (toList)
import           Data.Text            (Text)
import           Database.Bolt        (Node (..), URelationship (..),
                                       Value (..))
import           Database.Bolt.Extras (BoltId)

-- | Class for Selectors, which can update identifier, labels and props.
--
class SelectorLike a where
   withIdentifier :: Text -> a -> a
   withLabel      :: Text -> a -> a
   withProp       :: (Text, Value) -> a -> a

-- | Selector for 'Node's.
--
-- This datatype has @OverloadedLabels@ instance to simplify specifying nodes. Labels produce
-- empty nodes.
--
-- > #foo :: NodeSelector
-- > -- foo = NodeSelector (Just "foo") [] []
--
data NodeSelector = NodeSelector { nodeIdentifier :: Maybe Text
                                 , nodeLabels     :: [Text]
                                 , nodeProperties :: [(Text, Value)]
                                 }
  deriving (Show, Eq)

-- | Selector for 'URelationship's.
--
-- This datatype has @OverloadedLabels@ instance as well, similar to 'NodeSelector'.
data RelSelector = RelSelector { relIdentifier :: Maybe Text
                               , relLabel      :: Text
                               , relProperties :: [(Text, Value)]
                               }
  deriving (Show, Eq)

-- | Operator version of 'withLabel'. To be used with @OverloadedLabels@ instances.
--
-- > #foo .: "Foo" :: NodeSelector
--
infixl 9 .:
(.:) :: SelectorLike a => a -> Text -> a
(.:) = flip withLabel

-- | Operator version of 'withProp'. To be used with @OverloadedLabels@ instances.
--
-- See also 'Database.Bolt.=:' from @Database.Bolt@ package.
--
-- > #foo .# ["bar" =: 42, "baz" =: "baz"] :: NodeSelector
--
infixl 9 .#
(.#) :: SelectorLike a => a -> [(Text, Value)] -> a
(.#) = foldl' (flip withProp)

(#) :: a -> (a -> b) -> b
(#) = flip ($)

-- | Selector for paths.
--
infixl 2 :!->:
infixl 2 :!-:
data PathPart = RelSelector :!->: NodeSelector -- ^ directed relation
              | RelSelector :!-: NodeSelector  -- ^ not directed relation
  deriving (Show, Eq)

infixl 1 :-!:
infixl 1 :<-!:
data PathSelector = PathSelector :-!: PathPart  -- ^ not directed relation
                  | PathSelector :<-!: PathPart -- ^ directed relation
                  | P NodeSelector              -- ^ starting node of Path
  deriving (Show, Eq)

-- | Combined version of ':-!:' and 'P' for specifying the first node of path.
--
infixl 1 -:
(-:) :: NodeSelector -> PathPart -> PathSelector
ns -: pp = P ns :-!: pp

-- | Combined version of ':<-!:' and 'P' for specifying the first node of path.
--
infixl 1 <-:
(<-:) :: NodeSelector -> PathPart -> PathSelector
ns <-: pp = P ns :<-!: pp

data Selector = PS PathSelector -- ^ path selector
              | TS Text         -- ^ free text selector
  deriving (Show, Eq)

type Selectors = [Selector]

-- | Conditions.
--
data Cond = ID Text BoltId    -- ^ ID(txt) = boltId
          | IDs Text [BoltId] -- ^ ID(txt) IN [boltId1, boltId2, ... ]
          | IN Text [Text]    -- ^ txt IN [txt1, txt2, ... ]
          | TC Text           -- ^ free text condition
  deriving (Show, Eq)

infixr 3 :&&:
infixr 2 :||:
data Conds = Conds :&&: Conds -- ^ 'condition' AND 'condition'
           | Conds :||: Conds -- ^ 'condition' OR 'condition'
           | C Cond           -- ^ single 'condition'
           | Not Conds        -- ^ NOT 'condition'
  deriving (Show, Eq)

-- | Expression in Cypher language.
--
data Expr next = Create Selectors next        -- ^ CREATE query
               | Match Selectors next         -- ^ MATCH query
               | OptionalMatch Selectors next -- ^ OPTIONAL MATCH query
               | Merge Selectors next         -- ^ MERGE query
               | Where Conds next             -- ^ WHERE query
               | Set [Text] next              -- ^ SET query
               | Delete [Text] next           -- ^ DELETE query
               | DetachDelete [Text] next     -- ^ DETACH DELETE query
               | Remove [Text] next           -- ^ REMOVE query
               | Return [Text] next           -- ^ RETURN query
               | With [Text] next             -- ^ WITH query
               | Text Text next               -- ^ free text query
  deriving (Show, Eq, Functor)

-- | Empty 'NodeSelector'.
defaultNode :: NodeSelector
defaultNode = NodeSelector Nothing [] []

-- | Shorter synonym for 'defaultRel'.
defN :: NodeSelector
defN = defaultNode

-- | Empty 'RelSelector'.
defaultRel :: RelSelector
defaultRel = RelSelector Nothing "" []

-- | Shorter synonym for 'defaultRel'.
defR :: RelSelector
defR = defaultRel

toNodeSelector :: Node -> NodeSelector
toNodeSelector Node{..} = defaultNode { nodeLabels      = labels
                                      , nodeProperties  = filter ((/= N ()) . snd) (toList nodeProps)
                                      }

toRelSelector :: URelationship -> RelSelector
toRelSelector URelationship{..} = defaultRel { relLabel      = urelType
                                             , relProperties = toList urelProps
                                             }