{-# LANGUAGE UnicodeSyntax, FlexibleContexts #-}
-- | Functions for modifying the graph. Although the graph structure is entirely expressed by the graph's node collection, for convenience and efficiency the graph representation also comprises a complementary collection of edges, that has to be synchronised with the node collection. Therefore each of the functions below involves a test for whether the graph structure has been changed, and if so, measures are taken to ensure the graph remains consistent.

-- Invariants for graph consistency:
-- - ∀n∊N ∀e∊E: n→e ⇔ e→n
-- - ∀e∊E ∃n∊N: e→n
module GraphRewriting.Graph.Write
	(module GraphRewriting.Graph.Write, module GraphRewriting.Graph.Types, module Data.View)

import Prelude.Unicode
import GraphRewriting.Graph.Types
import GraphRewriting.Graph.Internal
import GraphRewriting.Graph.Read
import qualified GraphRewriting.Graph.Write.Unsafe as Unsafe
import Control.Monad
import Data.Maybe (catMaybes)
import Data.View
import Data.List
import qualified Data.IntMap as Map
import qualified Data.IntSet as Set

-- | assign new value to given node
writeNode  View [Port] n  Node  n  Rewrite n ()
writeNode r = modifyNode r . const

-- | modify the node value
modifyNode  View [Port] n  Node  (n  n)  Rewrite n ()
modifyNode n@(Node i) f = do
	esBefore  liftM nub (inspectNode n)
	Unsafe.modifyNode n f
	esAfter  liftM nub (inspectNode n)
	Unsafe.register n (esAfter \\ esBefore)
	Unsafe.unregister n (esBefore \\ esAfter)

-- | Wraps 'update' to update aspect @v@ of a node.
updateNode  (View [Port] n, View v n)  Node  v  Rewrite n ()
updateNode n = adjustNode n . const

-- | Wraps 'adjust' to adjust aspect @v@ of a node.
adjustNode  (View [Port] n, View v n)  Node  (v  v)  Rewrite n ()
adjustNode n = modifyNode n . adjust

adjustNodeM  (View [Port] n, View v n)  Node  (v  Rewrite n v)  Rewrite n ()
adjustNodeM n f = do
	v'  f =<< inspectNode n
	updateNode n v'

-- | add a new node with value @n@ to the graph
newNode  View [Port] n  n  Rewrite n Node
newNode v = do
	key  newRef
	let n = Node key
	modifyNodeMap $ Map.insert key v
	Unsafe.register n (inspect v)
	return n

-- | Create a new node by cloning another, at the same time updating aspect @v@. When defining rewrites in a context where it is not known what type @n@ the nodes of the graph have, this is the only way to add new nodes to the graph.
copyNode  (View [Port] n, View v n)  Node  v  Rewrite n Node
copyNode n f = newNode . update f =<< readNode n

-- | Create a new (unconnected) edge. It is expected that the created edge is connected to a port sooner or later. Otherwise the graph will invove unconnected edges.
newEdge  Rewrite n Edge
newEdge = liftM Edge newRef

-- | remove node from the graph
deleteNode  View [Port] n  Node  Rewrite n ()
deleteNode n = do
	es  liftM nub (inspectNode n)
	Unsafe.unregister n es
	modifyNodeMap (Map.delete $ nKey n)

-- | Disconnect ports connected to the given edge by assigning a new (dangling) edge to each of the ports. Then the edge is deleted.
deleteEdge  View [Port] n  Edge  Rewrite n [Edge]
deleteEdge e = do
	es  liftM concat $ mapM disconnectPorts =<< attachedNodes e
	modifyEdgeMap $ Map.delete (eKey e)
	return es
	disconnectPorts n = do
		ports  inspectNode n
		(freshEdges, ports')  liftM unzip $ mapM substPort ports
		updateNode n ports'
		return $ catMaybes freshEdges
	substPort p = if p  e
		then do
			p'  newEdge
			return (Just p', p')
		else return (Nothing, p)

-- | Reconnects the ports connected to the second edge to the first one. Then the second edge is deleted.
mergeEdges  View [Port] n  Edge  Edge  Rewrite n ()
mergeEdges e1 e2 = when (e1  e2) $ do
		ns  attachedNodes e2
		mapM_ (Unsafe.adjustNode $ map replacePort) ns
		modifyEdgeMap $ Map.adjust (Set.union $ Set.fromList $ map nKey ns) (eKey e1)
		deleteEdge e2 >> return ()
	where replacePort p = if p  e2 then e1 else p