-- |
-- Module      : Data.Alpino.DepStruct.Triples
-- Copyright   : (c) 2011 Daniël de Kok
-- License     : Apache 2
--
-- Maintainer  : Daniël de Kok <me@danieldk.eu>
-- Stability   : experimental
--
-- Definition and extraction of Alpino dependency triples.

module Data.Alpino.DepStruct.Triples (

  -- * Dependency triples
  DepTriple(..),
  DepTripleComponent(..),
  depTriples,

  -- * Utility functions
  tzFold

) where

import Control.Monad (ap)
import Data.Maybe (catMaybes, fromJust)
import Data.Set (Set, fromList)
import Data.Tree.Zipper

import Data.Alpino.DepStruct

-- Dependency triples

-- | The 'DepTriple' type represents a dependency that occurs in
--   a dependency structure. The triple consists of the head, a dependent, and
--   the relation between the head and the dependeny. For convenience, the
--   triple is composed of two 'DepTripleComponent' instances: the first
--   representing the head and its role in the relation, the second
--   representing the dependant and its role in the relation.
data DepTriple = DepTriple {
  tripleHead :: DepTripleComponent,
  tripleDep  :: DepTripleComponent
} deriving (Eq, Ord, Show)

-- | The 'DepTripleComponent' type represents a head or a dependant in a
--   dependency relation.
data DepTripleComponent = DepTripleComponent {
  triplePos  :: String,
  tripleRoot :: String,
  tripleRel  :: Rel
} deriving (Eq, Ord, Show)

-- | Extract 'DepTriples' from the tree starting at the node represented by
--   the 'TreePos' zipper.
depTriples :: TreePos Full DSLabel -> Set DepTriple
depTriples =
  fromList . map (uncurry hdDepToTriple) . hdsDeps . heads
  where
    hdsDeps = concat . map hdDeps           -- Find dependencies of given heads.
    hdDeps = (zip . repeat) `ap` dependants -- Find dependencies of a head.

heads :: TreePos Full DSLabel -> [TreePos Full DSLabel]
heads =
  tzFilter isHead

isHead :: TreePos Full DSLabel -> Bool
isHead t = case label t of
  (LexLabel rel _ _ _ _ _) -> rel `elem` headRels
  _                    -> False

headRels :: [Rel]
headRels = [Hd, Cmp, Crd, DLink, Rhd, Whd]

-- Find dependants of a node. Dependants are:
--
-- * Siblings that are lexical nodes
-- * Heads of non-lexical nodes
--
dependants :: TreePos Full DSLabel -> [TreePos Full DSLabel]
dependants = catMaybes . map lexOrHdDtr . siblings

-- Get zippers for the siblings of a node.
siblings :: TreePos Full DSLabel ->
  [TreePos Full DSLabel]
siblings t =
  case parent t of
    (Just p) -> filter ((/=) t) $ childList p
    Nothing  -> [] -- No parent? No siblings.

-- Get zippers fo the children of a node.
childList :: TreePos Full DSLabel -> [TreePos Full DSLabel]
childList = curLevel . firstChild
  where
    curLevel (Nothing) = []
    curLevel (Just t') = t':curLevel (next t')

-- If the node is a lexical node, return it as-is. If not, return its
-- head daughter.
lexOrHdDtr :: TreePos Full DSLabel -> Maybe (TreePos Full DSLabel)
lexOrHdDtr t = 
  case label t of
    (LexLabel _ _ _ _ _ _) -> Just t
    (CatLabel _ _ _ _ _)   -> case filter isHead $ childList t of
                           [c] -> Just c
                           _   -> Nothing

-- Retrieve the relation of a node if it serves as a dependent.
relAsDependent :: TreePos Full DSLabel -> Maybe Rel
relAsDependent t =
  case label t of
    (LexLabel rel _ _ _ _ _) -> if rel `elem` headRels then
                              case parent t of
                                Just p -> case label p of
                                  (LexLabel rel' _ _ _ _ _) -> Just rel'
                                  (CatLabel rel' _ _ _ _)   -> Just rel'
                                Nothing -> Nothing
                            else
                             Just rel
    (CatLabel _ _ _ _ _)     -> Nothing

hdDepToTriple :: TreePos Full DSLabel -> TreePos Full DSLabel ->
  DepTriple
hdDepToTriple hd dep = DepTriple hdTripleComp depTripleComp
  where
    hdTripleComp = DepTripleComponent (labelPos hdLabel) (labelRoot hdLabel) (labelRel hdLabel)
    hdLabel = label hd
    depTripleComp = DepTripleComponent (labelPos depLabel) (labelRoot depLabel) (fromJust $ relAsDependent dep)
    depLabel = label dep

-- Utility functions

-- | Fold over a tree depth-first, starting at the node wrapped in the
--   'TreePos' zipper.
tzFold :: (a -> TreePos Full b -> a) -> a -> TreePos Full b -> a
tzFold f acc t =
  foldSiblings $ foldChildren $ f acc t
  where
    foldChildren acc' =
      case firstChild t of
        Just c  -> tzFold f acc' c
        Nothing -> acc'
    foldSiblings acc' =
      case next t of
        Just s  -> tzFold f acc' s
        Nothing -> acc'

tzFilter :: (TreePos Full b -> Bool) -> TreePos Full b -> [TreePos Full b]
tzFilter f =
  tzFold adder []
  where
    adder acc t
      | f t       = t:acc
      | otherwise = acc