{-# LANGUAGE OverloadedStrings  #-}

module Database.Neo4j.Relationship where


import Control.Exception.Lifted (throw, catch)
import Data.Aeson ((.=))
import Data.Maybe (fromMaybe)

import qualified Data.Aeson as J
import qualified Data.ByteString as S
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Network.HTTP.Types as HT

import Database.Neo4j.Node
import Database.Neo4j.Http
import Database.Neo4j.Types


-- | Get the ID of a relationship
relId :: Relationship -> S.ByteString
relId n = S.drop (pathLength + 1) (runRelIdentifier n)
    where pathLength = S.length relationshipAPI

-- | Gets all relationship types in the DB
allRelationshipTypes :: Neo4j [RelationshipType]
allRelationshipTypes = Neo4j $ \conn -> httpRetrieveSure conn "/db/data/relationship/types"

-- | Create a new relationship with a type and a set of properties
createRelationship :: RelationshipType -> Properties -> Node -> Node -> Neo4j Relationship
createRelationship t props nodefrom nodeto = Neo4j $ \conn -> do
            res <- httpCreate4XXExplained conn reqPath reqBody
            case res of
                        Right rel -> return rel
                        Left expl -> wrapExc expl
    where reqPath = runNodeIdentifier nodefrom <> "/relationships"
          reqBody = J.encode $ J.object ["to" .= runNodeUrl (nodeUrl nodeto), "type" .= t,
                                         "data" .= J.toJSON props]
          wrapExc msg
            | msg == "StartNodeNotFoundException" = throw (Neo4jNoEntityException $ runNodeIdentifier nodefrom)
            | msg == "EndNodeNotFoundException" = throw (Neo4jNoEntityException $ runNodeIdentifier nodeto)
            | otherwise = throw (Neo4jHttpException $ T.unpack msg)

-- | Refresh a relationship entity with the contents in the DB
getRelationship :: RelIdentifier a => a -> Neo4j (Maybe Relationship)
getRelationship rel = Neo4j $ \conn -> httpRetrieve conn (runRelIdentifier rel)

-- | Get the "node from" from a relationship from the DB
-- | Raises Neo4jNoEntityException if the node (and thus the relationship) does not exist any more
getRelationshipFrom :: Relationship -> Neo4j Node
getRelationshipFrom rel = getNode node >>= processMaybe
    where node = relFrom rel
          processMaybe n = return $ fromMaybe (throw $ Neo4jNoEntityException (runNodeIdentifier node)) n

-- | Get the "node to" from a relationship from the DB
-- | Raises Neo4jNoEntityException if the node (and thus the relationship) does not exist any more
getRelationshipTo :: Relationship -> Neo4j Node
getRelationshipTo rel = getNode node >>= processMaybe
    where node = relTo rel
          processMaybe n = return $ fromMaybe (throw $ Neo4jNoEntityException (runNodeIdentifier node)) n 

-- | Delete a relationship
deleteRelationship :: RelIdentifier a => a -> Neo4j ()
deleteRelationship rel = Neo4j $ \conn -> httpDelete conn (runRelIdentifier rel)

-- | Get all relationships for a node, if the node has disappeared it will raise an exception
getRelationships :: Node -> Direction -> [RelationshipType] -> Neo4j [Relationship]
getRelationships n dir types = Neo4j $ \conn -> 
      httpRetrieveSure conn (runNodeIdentifier n <> "/relationships/" <> dirStr dir <> filterStr types) `catch` procExc
    where dirStr Outgoing = "out"
          dirStr Incoming = "in"
          dirStr Any = "all"
          filterStr [] = ""
          filterStr f = "/" <> TE.encodeUtf8 (T.intercalate "%26" f)
          procExc exc@(Neo4jUnexpectedResponseException s)
                  | s == HT.status404 = throw (Neo4jNoEntityException $ runNodeIdentifier n)
                  | otherwise = throw exc
          procExc exc = throw exc