module DataFlow.Validation (
    ValidationError(..),
    validate
) where

import Data.Set (Set, member, insert, empty)
import Text.Printf

import DataFlow.Core

data ValidationError = UnknownID ID
                     | DuplicateDeclaration ID
                     deriving (Eq)

instance Show ValidationError where
  show (UnknownID i) = printf "Unknown ID: %s" i
  show (DuplicateDeclaration i) = printf "Duplicate declaration of ID: %s" i

getNodeIDs :: Diagram -> [ID]
getNodeIDs (Diagram _ nodes _) = concatMap getRootNodeId nodes
  where getId (InputOutput i _) = [i]
        getId (Function i _) = [i]
        getId (Database i _) = [i]
        getRootNodeId (Node node) = getId node
        getRootNodeId (TrustBoundary _ _ nodes) = concatMap getId nodes

getBoundaryIDs :: Diagram -> [ID]
getBoundaryIDs (Diagram _ nodes _) = concatMap getRootNodeId nodes
  where getRootNodeId (TrustBoundary id _ _) = [id]
        getRootNodeId _ = []

validateDuplicateIDs :: [ID] -> Either [ValidationError] (Set ID)
validateDuplicateIDs ids =
  case foldl iter (empty, []) ids of
    (seen, []) -> Right seen
    (_, errors) -> Left errors
  where iter (seen, errors) i = if i `member` seen
                                  then (seen, errors ++ [DuplicateDeclaration i])
                                  else (insert i seen, errors)

validateFlowIDs :: Diagram -> Set ID -> Either [ValidationError] ()
validateFlowIDs (Diagram _ _ flows) ids =
  case foldl iter [] flows of
    [] -> Right ()
    errors -> Left errors
  where idError i = if i `member` ids then [] else [UnknownID i]
        iter errors (Flow source target _) =
          errors ++ idError source ++ idError target

validate :: Diagram -> Either [ValidationError] Diagram
validate diagram = do
  nodeIDs <- validateDuplicateIDs (getNodeIDs diagram)
  validateDuplicateIDs (getBoundaryIDs diagram)
  validateFlowIDs diagram nodeIDs
  return diagram