-- Pinpoint
-- By Gregory W. Schwartz

module Math.Clumpiness.Pinpoint where

-- Built-in
import Data.Maybe
import qualified Data.Map as M
import qualified Data.Set as Set
import qualified Data.Sequence as Seq
import Data.Monoid
import Data.Tree
import Control.Arrow

-- Cabal
import Math.TreeFun.Tree
import Math.TreeFun.Types

-- Local
import Math.Clumpiness.Types
import Math.Clumpiness.Utilities
import Math.Clumpiness.Algorithms

-- | Filter the PropertyMap based on the tree, getting rid of extra vertices
getValidPropertyMap :: (Ord a, Ord b)
                    => Tree (SuperNode a)
                    -> PropertyMap a b
                    -> PropertyMap a b
getValidPropertyMap tree = M.filterWithKey (\k _ -> Set.member k vertices)
  where
    vertices = Set.fromList . map myRootLabel . flatten $ tree

-- | Convert a subtree first node to a root
rootSubtree :: Tree (SuperNode a) -> Tree (SuperNode a)
rootSubtree ( Node { rootLabel = SuperNode  { myRootLabel = x
                                            , myLeaves = y
                                            }
                   , subForest = xs
                   }
            ) =
    Node { rootLabel = SuperNode  { myRootLabel = x
                                  , myLeaves = y
                                  , myParent = SuperRoot
                                  }
         , subForest = xs
         }

-- | Check if a node belongs to a label
viableNode :: (Ord a, Ord b) => (b -> Bool) -> PropertyMap a b -> a -> Bool
viableNode viable propertyMap x = (>= Just 0)
                                . fmap (Seq.length . fmap viable)
                                . M.lookup x
                                $ propertyMap

-- | Assign the clumpiness to each subtree and add them to a list. Ignore
-- if the vertex is a root
pinpointRecursion :: (Ord a, Ord b)
                  => (b -> Bool)
                  -> PropertyMap a b
                  -> Tree (SuperNode a)
                  -> Seq.Seq (Pinpoint a b)
pinpointRecursion _ _ (Node { subForest = [] }) = Seq.empty
pinpointRecursion viable propertyMap tree@( Node { rootLabel = SuperNode { myRootLabel = label
                                                                         , myLeaves    = descendents
                                                                         , myParent    = parent
                                                                         }
                                                 , subForest = xs }
                                          ) =
    case newPinpoint of
        Nothing  -> continue
        (Just x) -> x Seq.<| continue
  where
    continue       = mconcat
                   . map (pinpointRecursion viable validPropertyMap)
                   $ xs
    newPinpoint    = case parent of
                         SuperRoot -> Nothing
                         _         -> Just
                                      Pinpoint { pinpointLabel = label
                                               , pinpointClumpiness = clump
                                               , pinpointLeaves = relevantLeaves
                                               }
    relevantLeaves = Seq.fromList
                   . filter (viableNode viable validPropertyMap)
                   . M.keys
                   $ descendents
    clump  = Seq.fromList
           $ generateClumpMap viable validPropertyMap newTree
    validPropertyMap = getValidPropertyMap newTree propertyMap
    newTree          = filterRootLeaves . rootSubtree $ tree

-- | Return the clumpiness vertices in the tree based on the minimum
-- clumpiness and the minimum number of descendent leaves. Here, viable is
-- the same usage as in Algorithms. The clumpiness values are only reported
-- if they are above a threshold of minClumpiness.
pinpoint :: (Ord a, Ord b) => Double
                           -> Int
                           -> (b -> Bool)
                           -> PropertyMap a b
                           -> Tree (SuperNode a)
                           -> Seq.Seq (Pinpoint a b)
pinpoint minClumpiness minLeaves viable propertyMap tree =
      Seq.filter
      ( \x -> (not . Seq.null . pinpointClumpiness $ x)
           && (Seq.length . pinpointLeaves $ x) >= minLeaves
      )
    . fmap (\x -> x { pinpointClumpiness = filterClumpSeq
                                         . pinpointClumpiness
                                         $ x
                    }
           )
    $ pinpointList
  where
    pinpointList             = pinpointRecursion viable propertyMap tree
    validPropertyMap subtree = getValidPropertyMap subtree propertyMap
    filterClumpSeq           = Seq.filter ((>= minClumpiness) . thd')