-- Communicating Haskell Processes.
-- Copyright (c) 2008, University of Kent.
-- All rights reserved.
-- 
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
--  * Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
--  * Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
--  * Neither the name of the University of Kent nor the names of its
--    contributors may be used to endorse or promote products derived from
--    this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

module Control.Concurrent.CHP.Traces.Base where

import Control.Concurrent.STM
import Control.Monad.State
import Data.List
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Unique

import Control.Concurrent.CHP.Event
import Control.Concurrent.CHP.ProcessId

-- | A globally recorded event, found in CSP and VCR traces.  Either a
-- channel communication (with a unique identifier of the channel) or a
-- barrier synchronisation (with a unique identifier of the barrier).  The
-- identifiers are per channel\/barrier, not per event.  Currently,
-- channels and barriers can never have the same Unique as each other, but
-- do not rely on this behaviour.
type RecordedEvent = (RecordedEventType, Unique)

-- | An individual record of an event, found in nested traces.  Either a
-- channel write or read, or a barrier synchronisation, each with a unique
-- identifier for the barrier\/channel.  The event will be recorded by
-- everyone involved, and a ChannelWrite will have the same channel
-- identifier as the corresponding channel-read.  The identifiers are per
-- channel\/barrier, not per event.  Currently, channels and barriers can
-- never have the same Unique as each other, but do not rely on this
-- behaviour.
--
-- TimerSyncIndiv was added in version 1.2.0.
data RecordedIndivEvent = 
  ChannelWrite Unique
  | ChannelRead Unique
  | BarrierSyncIndiv Unique
  | ClockSyncIndiv Unique String
  deriving (Eq, Ord)

type RecEvents = ([RecordedEvent], [RecordedIndivEvent])

getName :: String -> Unique -> State (Map.Map Unique String) String
getName prefix u
          = do m <- get
               case Map.lookup u m of
                 Just x -> return x
                 Nothing -> let x = prefix ++ show (Map.size m) in
                            do put $ Map.insert u x m
                               return x

nameEvent :: RecordedEvent -> State (Map.Map Unique String) String
nameEvent (t, c) = liftM (++ suffix) $ getName prefix c
  where
    (prefix, suffix) = case t of
      ChannelComm -> ("_c","")
      BarrierSync -> ("_b","")
      ClockSync st -> ("_t", ':' : st)

nameIndivEvent :: RecordedIndivEvent -> State (Map.Map Unique String) String
nameIndivEvent (ChannelWrite c) = do c' <- getName "_c" c
                                     return $ c' ++ "!"
nameIndivEvent (ChannelRead c) = do c' <- getName "_c" c
                                    return $ c' ++ "?"
nameIndivEvent (BarrierSyncIndiv c) = do c' <- getName "_b" c
                                         return $ c' ++ "*"
nameIndivEvent (ClockSyncIndiv c t) = do c' <- getName "_t" c
                                         return $ c' ++ ":" ++ t


ensureAllNamed :: Map.Map Unique String -> [RecordedEvent] -> Map.Map Unique String
-- Quite hacky:
ensureAllNamed m es = execState (mapM_ nameEvent es) m

ensureAllNamedIndiv :: Map.Map Unique String -> [RecordedIndivEvent] -> Map.Map Unique String
-- Quite hacky:
ensureAllNamedIndiv m es = execState (mapM_ nameIndivEvent es) m


-- The list of integers is for alting
type TraceT = StateT ([Int], TraceStore)

data TraceStore =
  NoTrace
  | Trace (ProcessId, TVar ChannelLabels, SubTraceStore)

type ChannelLabels = Map.Map Unique String

data SubTraceStore =
  Hierarchy (Structured RecordedIndivEvent)
  | CSPTraceRev (TVar [(Int, [RecordedEvent])])
  | VCRTraceRev (TVar [Set.Set (Set.Set ProcessId, RecordedEvent)])

data Ord a => Structured a =
  StrEvent a
  | Par [Structured a]
  | RevSeq [(Int, [Structured a])]
  deriving (Eq, Ord)

-- | Records an event where you were the last person to engage in the event
recordEventLast :: [(RecordedEvent, Set.Set ProcessId)] -> TraceStore -> STM ()
recordEventLast news y
           =    case y of
                  Trace (_,_,CSPTraceRev tv) ->
                    do t <- readTVar tv
                       writeTVar tv $! foldl (flip addRLE) t (map fst news)
                  Trace (pid, _, VCRTraceRev tv) -> do
                    t <- readTVar tv
                    let pidSet = (foldl Set.union (Set.singleton pid) $ map snd news)
                        news' = map (\(a,b) -> (b,a)) news
                        t' = case t of
                                -- Trace previously empty:
                               [] -> [Set.fromList news']
                               (z:zs) | shouldMakeNewSetVCR pidSet z
                                         -> Set.fromList news' : t
                                      | otherwise
                                          -> foldl (flip Set.insert) z news' : zs
                    writeTVar tv $! t'
                  _ -> return ()

-- | Records an event where you were one of the people involved
recordEvent :: [RecordedIndivEvent] -> TraceT IO ()
recordEvent e
           = do (x,y) <- get
                case (x, y) of
                  (as, Trace (pid,tvls,Hierarchy es)) ->
                       put (as, Trace (pid, tvls, Hierarchy (foldl (flip addSeqEventH) es e)))
                  _ -> return ()

mergeSubProcessTraces :: [TraceStore] -> TraceT IO ()
mergeSubProcessTraces ts
  = do s <- get
       case s of
         (as, Trace (pid, tvls, Hierarchy es)) ->
           put (as, Trace (pid, tvls, Hierarchy $ addParEventsH ts' es))
           where ts' = [t | Trace (_,_,Hierarchy t) <- ts]
         _ -> return ()


shouldMakeNewSetVCR :: Set.Set ProcessId -> Set.Set (Set.Set ProcessId, RecordedEvent)
  -> Bool
shouldMakeNewSetVCR newIds existingSet
  = exists existingSet $ \(bigP,_) -> exists bigP $ \p -> exists newIds $ \q ->
      p `pidLessThanOrEqual` q
  where
    -- Like the any function (flipped), but for sets:
    exists :: Ord a => Set.Set a -> (a -> Bool) -> Bool
    exists s f = not . Set.null $ Set.filter f s


compress :: (Eq a, Ord a) => Structured a -> Structured a
compress (RevSeq ((1,s):(n,s'):ss))
  | n == 1 && (s `isPrefixOf` s') = compress $ RevSeq $ (2,s):(1,drop (length s) s'):ss
  | s == s' = compress $ RevSeq $ (n+1,s'):ss
compress x = x


addParEventsH :: (Eq a, Ord a) => [Structured a] -> Structured a -> Structured a
addParEventsH es t = let n = es in case t of
  StrEvent _ -> RevSeq [(1, [Par n, t])]
  Par _ -> RevSeq [(1, [Par n, t])]
  RevSeq ((1,s):ss) -> compress $ RevSeq $ (1,Par n : s) : ss
  RevSeq ss -> compress $ RevSeq $ (1, [Par n]):ss

addSeqEventH :: (Eq a, Ord a) => a -> Structured a -> Structured a
addSeqEventH e (StrEvent e') = RevSeq [(1,[StrEvent e, StrEvent e'])]
addSeqEventH e (Par p) = RevSeq [(1,[StrEvent e, Par p])]
addSeqEventH e (RevSeq ((1,s):ss))
  | (StrEvent e) `notElem` s = compress $ RevSeq $ (1,StrEvent e:s):ss
addSeqEventH e (RevSeq ss) = compress $ RevSeq $ (1,[StrEvent e]):ss


addRLE :: Eq a => a -> [(Int,[a])] -> [(Int,[a])]
-- Single event in most recent list:
addRLE x ((n,[e]):nes)
  -- If we have the same event, bump up the count:
  | x == e = (n+1,[e]):nes
-- Only one list thus far
addRLE x allEs@[(1,es)]
  -- If we are the same as the most recent event, break it off and aggregate:
  | x == head es = [(2, [x]), (1, tail es)]
  -- If we're already in that list, start a new one:
  | x `elem` es = (1,[x]):allEs
  -- Otherwise join the queue!
  | otherwise = [(1,x:es)]
-- Most recent list has count of 1
addRLE x allEs@((1,es):(n,es'):nes)
  -- If adding us to the most recent list forms an identical list to the one
  -- before that, aggregate
  | x == head es' && es == tail es' = (n+1,es'):nes
  -- If we're already in that list, start a new one:
  | x `elem` es = (1,[x]):allEs
  -- Otherwise, join the most recent list
  | otherwise = (1,x:es):(n,es'):nes
-- If no special cases hold, start a new list:
addRLE x nes = (1,[x]):nes


labelEvent :: Event -> String -> StateT (a, TraceStore) IO ()
labelEvent e l
  = labelUnique (getEventUnique e) l

labelUnique :: Unique -> String -> StateT (a, TraceStore) IO ()
labelUnique u l
  = do (_,t) <- get
       case t of
         NoTrace -> return ()
         Trace (_,tvls,_) -> add tvls
  where
    add :: TVar (Map.Map Unique String) -> StateT (a, TraceStore) IO ()
    add tv = liftIO $ atomically $ do
      m <- readTVar tv
      writeTVar tv $ Map.insert u l m


blankTraces :: TraceStore -> Int -> IO [TraceStore]
blankTraces NoTrace n = return $ replicate n NoTrace
blankTraces (Trace (pid, tvls, subT)) n =
  return [Trace (newId, tvls, newSubT) | newId <- newIds]
  where
    newIds :: [ProcessId]
    newIds = let ProcessId parts = pid in
      [ProcessId $ parts ++ [ParSeq i 0] | i <- [0 .. (n - 1)]]

    newSubT :: SubTraceStore
    newSubT = case subT of
      Hierarchy {} -> Hierarchy $ RevSeq []
      _ -> subT