module GUI.TraceView (
    TraceView,
    traceViewNew,
    TraceViewActions(..),
    traceViewSetHECs,
    traceViewGetTraces,
  ) where

import Events.HECs
import GUI.Types

import Graphics.UI.Gtk
import qualified Graphics.UI.Gtk.ModelView.TreeView.Compat as Compat
import Data.Tree


-- | Abstract trace view object.
--
data TraceView = TraceView {
       tracesStore :: TreeStore (Trace, Visibility)
     }

data Visibility = Visible | Hidden | MixedVisibility
  deriving Eq

-- | The actions to take in response to TraceView events.
--
data TraceViewActions = TraceViewActions {
       traceViewTracesChanged :: [Trace] -> IO ()
     }

traceViewNew :: Builder -> TraceViewActions -> IO TraceView
traceViewNew builder actions = do

    tracesTreeView <- builderGetObject builder  castToTreeView "traces_tree"

    tracesStore <- treeStoreNew []
    traceColumn <- treeViewColumnNew
    textcell    <- cellRendererTextNew
    togglecell  <- cellRendererToggleNew

    let traceview = TraceView {..}

    treeViewColumnPackStart traceColumn textcell   True
    treeViewColumnPackStart traceColumn togglecell False
    treeViewAppendColumn tracesTreeView traceColumn

    Compat.treeViewSetModel tracesTreeView (Just tracesStore)

    cellLayoutSetAttributes traceColumn textcell tracesStore $ \(tr, _) ->
      [ cellText := renderTrace tr ]

    cellLayoutSetAttributes traceColumn togglecell tracesStore $ \(_, vis) ->
      [ cellToggleActive       := vis == Visible
      , cellToggleInconsistent := vis == MixedVisibility ]

    on togglecell cellToggled $ \str ->  do
      let path = stringToTreePath str
      Node (trace, visibility) subtrees <- treeStoreGetTree tracesStore path
      let visibility' = invertVisibility visibility
      treeStoreSetValue tracesStore path (trace, visibility')
      updateChildren tracesStore path subtrees visibility'
      updateParents tracesStore (init path)

      traceViewTracesChanged actions =<< traceViewGetTraces traceview

    return traceview

  where
    renderTrace (TraceHEC           hec) = "HEC " ++ show hec
    renderTrace (TraceInstantHEC    hec) = "HEC " ++ show hec
    renderTrace (TraceCreationHEC   hec) = "HEC " ++ show hec
    renderTrace (TraceConversionHEC hec) = "HEC " ++ show hec
    renderTrace (TracePoolHEC       hec) = "HEC " ++ show hec
    renderTrace (TraceHistogram)         = "Spark Histogram"
    renderTrace (TraceGroup       label) = label
    renderTrace (TraceActivity)          = "Activity Profile"

    updateChildren tracesStore path subtrees visibility' =
      sequence_
        [ do treeStoreSetValue tracesStore path' (trace, visibility')
             updateChildren tracesStore path' subtrees' visibility'
        | (Node (trace, _) subtrees', n) <- zip subtrees [0..]
        , let path' = path ++ [n] ]

    updateParents :: TreeStore (Trace, Visibility) -> TreePath -> IO ()
    updateParents _           []   = return ()
    updateParents tracesStore path = do
      Node (trace, _) subtrees <- treeStoreGetTree tracesStore path
      let visibility = accumVisibility  [ vis | subtree  <- subtrees
                                              , (_, vis) <- flatten subtree ]
      treeStoreSetValue tracesStore path (trace, visibility)
      updateParents tracesStore (init path)

    invertVisibility Hidden = Visible
    invertVisibility _      = Hidden

    accumVisibility = foldr1 (\a b -> if a == b then a else MixedVisibility)

-- Find the HEC traces in the treeStore and replace them
traceViewSetHECs :: TraceView -> HECs -> IO ()
traceViewSetHECs TraceView{tracesStore} hecs = do
    treeStoreClear tracesStore
    -- for testing only (e.g., to compare with histogram of data from interval
    -- or to compare visually with other traces):
    -- treeStoreInsert tracesStore [] 0 (TraceHistogram, Visible)
    go 0
    treeStoreInsert tracesStore [] 0 (TraceActivity, Visible)
  where
    newT = Node { rootLabel = (TraceGroup "HEC Traces", Visible),
                  subForest = [ Node { rootLabel = (TraceHEC k, Visible),
                                       subForest = [] }
                              | k <- [ 0 .. hecCount hecs - 1 ] ] }
    newI = Node { rootLabel = (TraceGroup "Instant Events", Hidden),
                  subForest = [ Node { rootLabel = (TraceInstantHEC k, Hidden),
                                       subForest = [] }
                              | k <- [ 0 .. hecCount hecs - 1 ] ] }
    nCre = Node { rootLabel = (TraceGroup "Spark Creation", Hidden),
                  subForest = [ Node { rootLabel = (TraceCreationHEC k, Hidden),
                                       subForest = [] }
                              | k <- [ 0 .. hecCount hecs - 1 ] ] }
    nCon = Node { rootLabel = (TraceGroup "Spark Conversion", Hidden),
                  subForest = [ Node { rootLabel = (TraceConversionHEC k, Hidden),
                                       subForest = [] }
                              | k <- [ 0 .. hecCount hecs - 1 ] ] }
    nPoo = Node { rootLabel = (TraceGroup "Spark Pool", Hidden),
                  subForest = [ Node { rootLabel = (TracePoolHEC k, Hidden),
                                       subForest = [] }
                              | k <- [ 0 .. hecCount hecs - 1 ] ] }
    go n = do
      m <- treeStoreLookup tracesStore [n]
      case m of
        Nothing -> do
          treeStoreInsertTree tracesStore [] 0 nPoo
          treeStoreInsertTree tracesStore [] 0 nCon
          treeStoreInsertTree tracesStore [] 0 nCre
          treeStoreInsertTree tracesStore [] 0 newI
          treeStoreInsertTree tracesStore [] 0 newT
        Just t  ->
          case t of
             Node { rootLabel = (TraceGroup "HEC Traces", _) } -> do
               treeStoreRemove tracesStore [n]
               treeStoreInsertTree tracesStore [] n newT
               go (n+1)
             Node { rootLabel = (TraceGroup "HEC Instant Events", _) } -> do
               treeStoreRemove tracesStore [n]
               treeStoreInsertTree tracesStore [] n newI
               go (n+1)
             Node { rootLabel = (TraceGroup "Spark Creation", _) } -> do
               treeStoreRemove tracesStore [n]
               treeStoreInsertTree tracesStore [] n nCre
               go (n+1)
             Node { rootLabel = (TraceGroup "Spark Conversion", _) } -> do
               treeStoreRemove tracesStore [n]
               treeStoreInsertTree tracesStore [] n nCon
               go (n+1)
             Node { rootLabel = (TraceGroup "Spark Pool", _) } -> do
               treeStoreRemove tracesStore [n]
               treeStoreInsertTree tracesStore [] n nPoo
               go (n+1)
             Node { rootLabel = (TraceActivity, _) } -> do
               treeStoreRemove tracesStore [n]
               go (n+1)
             _ ->
               go (n+1)

traceViewGetTraces :: TraceView -> IO [Trace]
traceViewGetTraces TraceView{tracesStore} = do
  f <- getTracesStoreContents tracesStore
  return [ t | (t, Visible) <- concatMap flatten f, notGroup t ]
 where
  notGroup (TraceGroup _) = False
  notGroup _              = True

getTracesStoreContents :: TreeStore a -> IO (Forest a)
getTracesStoreContents tracesStore = go 0
  where
  go !n = do
    m <- treeStoreLookup tracesStore [n]
    case m of
      Nothing -> return []
      Just t  -> do
        ts <- go (n+1)
        return (t:ts)
