{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Description: Simple Events
--
-- We implement the Source type and combinators for it.
module Util.Sources(
   Source,
      -- A Source x d represents something that stores a value of
      -- type x and sends change messages of type d.

      -- We instance CanAddSinks (Source x d) x d

   Client,
      -- A Client d is something that consumes change messages of type d.

   -- Producer side
   staticSource, -- :: x -> Source x d
      -- returns a source which never changes

   staticSourceIO, -- :: IO x -> Source x d
      -- returns a source which never changes but gets its initial value
      -- from an IO action.

   variableSource, -- :: x -> IO (Source x d,(x -> (x,[d])) -> IO ())
      -- returns a source which can change.  The supplied action
      -- changes it.

   variableGeneralSource,
      -- :: x -> IO (Source x d,Updater x d)
      -- Like variableSource, but allows the provider of new values to
      -- get out an extra value.  For this it is necessary to go
      -- via the Updater type.

   Updater,
   applyToUpdater, -- :: Updater x d -> (x -> (x,[d],extra)) -> IO extra

   -- Client side
   attachClient, -- :: Client d -> Source x d -> IO x

   -- Transformers
   map1,
      -- :: (x1 -> x2) -> Source x1 d -> Source x2 d

   map1IO,
      -- :: (x1 -> IO x2) -> Source x1 d -> Source x2 d

   map2,
      -- :: (d1 -> d2) -> Source x d1 -> Source x d2
   filter2,
      -- :: (d1 -> Maybe d2) -> Source x d1 -> Source x d2

   filter2IO,
      -- :: (d1 -> IO (Maybe d2)) -> Source x d1 -> Source x d2
      -- To be used with care, since the IO action ties up the source.

   foldSource,
      -- :: (x -> state) -> (state -> d1 -> (state,d2))
      --    -> Source x d1 -> Source (state,x) d2

   foldSourceIO,
      -- :: (x1 -> IO (state,x2)) -> (state -> d1 -> IO (state,d2))
      -- -> Source x1 d1 -> Source (state,x2) d2

   stepSource,
      -- :: (x -> d2) -> (d1 -> d2) -> Source x d1 -> Source x d2
      -- This modifies the source so that whenever we attempt to read from it,
      -- the current "x" value is BOTH returned AND converted to an instant
      -- update (via the first function).

   -- Combinators
   choose,
      -- :: Source x1 d1 -> Source x2 d2 -> Source (x1,x2) (Either d1 d2)
   seqSource,
      -- :: Source x1 x1 -> (x1 -> Source x2 x2) -> Source x2 x2
   flattenSource,
      -- :: Source x [d] -> Source x d
      -- A Source combinator which "flattens" lists of updates.

   -- Monadic Sources
   SimpleSource(..),
      -- newtype for Source x x
      -- Instance of Functor and Monad

   staticSimpleSource, -- :: x -> SimpleSource x

   staticSimpleSourceIO, -- :: IO x -> SimpleSource x

   -- We also instance CanAddSinks (SimpleSource x) x x.
   -- This is done via the following class
   HasSource(..),
   HasSimpleSource(..),

   readContents,
      -- :: HasSource source x d => source -> IO x
      -- Get the current contents of the source, but don't specify any other
      -- action.

   -- miscellaneous handy utilities,
   mkHistorySource, -- :: (x -> d) -> Source x d -> Source x (d,d)
   mkHistorySimpleSource, -- :: x -> SimpleSource x -> SimpleSource (x,x)
   uniqSimpleSource, -- :: Eq x => SimpleSource x -> SimpleSource x

   pairSimpleSources,
      -- :: SimpleSource x1 -> SimpleSource x2 -> SimpleSource (x1,x2)
      -- Pair two SimpleSource's.  This is probably better than using >>=,
      -- since it does not require reregistering with the second SimpleSource

   sequenceSimpleSource, -- :: [SimpleSource x] -> SimpleSource [x]
   -- Does a similar job to pairSimpleSources, so that the sources run
   -- parallel.

   change1, -- :: SimpleSource x -> x -> SimpleSource x
   -- replaces the first value of the SimpleSource.

   mapIOSeq,
      -- :: SimpleSource a -> (a -> IO (SimpleSource b)) -> SimpleSource b
      -- allow us to sequence a SimpleSource where the continuation function
      -- uses an IO action.

   addNewSourceActions,
      -- :: Source x d -> (x -> IO ()) -> (d -> IO ())
      -- -> SinkID -> ParallelExec -> IO x
   -- Run the specified actions for the source, using the given SinkID and
   -- in the ParallelExec thread.
   -- The x -> IO () action is guaranteed to be performed before any of the
   -- d -> IO () actions.

   traceSimpleSource,
      -- :: (a -> String) -> SimpleSource a -> SimpleSource a
      -- Outputs information about what comes through the source, turning
      -- it into a String with the supplied function.  (This is done once
      -- for each active client.)

   traceSource,
      -- :: (a -> String) -> (d -> String) -> Source a d -> Source a d
      -- Like traceSimpleSource but for Source's.

   noLoopSimpleSource,
      -- :: TSem -> ([String] -> a) -> SimpleSource a -> SimpleSource a
      -- Used when we are worried that a SimpleSource recursively constructed
      -- by mapIOSeq, >>= and friends may actually try to call itself, and
      -- so loop forever.   The Strings identify the SimpleSource,
      -- and so the [String] is effectively a backtrace of the TSems,
      -- revealing what chain of simple sources might have caused the loop.

   mkIOSimpleSource,
      -- :: IO (SimpleSource a) -> SimpleSource a

   foldSimpleSourceIO,
      -- :: (x1 -> IO (state,x2)) -> (state -> x1 -> IO (state,x2))
      -- -> SimpleSource x1 -> SimpleSource x2

   ) where

import Data.Maybe

import Control.Concurrent
import Data.IORef

import Util.ExtendedPrelude(HasMapIO(..))
import Util.Sink
import Util.TSem
import Util.Debug(debug)

-- -----------------------------------------------------------------
-- Datatypes
-- -----------------------------------------------------------------

newtype Source x d = Source (Client d -> IO x)

newtype Client d = Client (d -> IO (Maybe (Client d)))

data SourceData x d = SourceData {
   x :: x,
   client :: Maybe (Client d)
   }

-- -----------------------------------------------------------------
-- Producer side
-- -----------------------------------------------------------------

staticSource :: x -> Source x d
staticSource x = Source (\ _ -> return x)


staticSourceIO :: IO x -> Source x d
staticSourceIO action = Source (\ _ -> action)

variableSource :: x -> IO (Source x d,(x -> (x,[d])) -> IO ())
variableSource x =
   do
      mVar <- newMVar (SourceData {
         x = x,
         client = Nothing
         })
      let
         update updateFn =
            do
               (SourceData {x = x1,client = clientOpt}) <- takeMVar mVar
               let
                  (x2,ds) = updateFn x1

                  sendUpdates (Just (Client clientFn)) (d:ds) =
                     do
                        newClientOpt <- clientFn d
                        sendUpdates newClientOpt ds
                  sendUpdates clientOpt _ = return clientOpt

               newClientOpt <- sendUpdates clientOpt ds
               putMVar mVar (SourceData {x = x2,client = newClientOpt})
         addClient newClient =
            do
               (SourceData {x = x,client = oldClientOpt}) <- takeMVar mVar
               let
                  fullNewClient = case oldClientOpt of
                     Nothing -> newClient
                     Just oldClient -> combineClients oldClient newClient
               putMVar mVar (SourceData {x = x,client = Just fullNewClient})
               return x
      return (Source addClient,update)


newtype Updater x d = Updater (forall extra . (x -> (x,[d],extra)) -> IO extra)

applyToUpdater :: Updater x d -> (x -> (x,[d],extra)) -> IO extra
applyToUpdater (Updater update) updateAct = update updateAct

variableGeneralSource :: x -> IO (Source x d,Updater x d)
variableGeneralSource x =
   do
      mVar <- newMVar (SourceData {
         x = x,
         client = Nothing
         })
      let
         update updateFn =
            do
               (SourceData {x = x1,client = clientOpt}) <- takeMVar mVar

               let
                  (x2,ds,extra) = updateFn x1
                  sendUpdates (Just (Client clientFn)) (d:ds) =
                     do
                        newClientOpt <- clientFn d
                        sendUpdates newClientOpt ds
                  sendUpdates clientOpt _ = return clientOpt

               newClientOpt <- sendUpdates clientOpt ds
               putMVar mVar (SourceData {x = x2,client = newClientOpt})
               return extra
         addClient newClient =
            do
               (SourceData {x = x,client = oldClientOpt}) <- takeMVar mVar
               let
                  fullNewClient = case oldClientOpt of
                     Nothing -> newClient
                     Just oldClient -> combineClients oldClient newClient
               putMVar mVar (SourceData {x = x,client = Just fullNewClient})
               return x
      return (Source addClient,Updater update)

combineClients :: Client d -> Client d -> Client d
combineClients (Client clientFn1) (Client clientFn2) =
   let
      clientFn d =
         do
            newClient1Opt <- clientFn1 d
            newClient2Opt <- clientFn2 d
            case (newClient1Opt,newClient2Opt) of
               (Nothing,Nothing) -> return Nothing
               (Just newClient1,Nothing) -> return (Just newClient1)
               (Nothing,Just newClient2) -> return (Just newClient2)
               (Just newClient1,Just newClient2)
                  -> return (Just (combineClients newClient1 newClient2))
   in
      Client clientFn

-- -----------------------------------------------------------------
-- Client side
-- -----------------------------------------------------------------

attachClient :: Client d -> Source x d -> IO x
attachClient client (Source addClient) = addClient client

-- | attachClientTemporary is like attach, but additionally returns an
-- IO action which can be used to prevent any client being run after that
-- IO action is called.
attachClientTemporary :: Client d -> Source x d -> IO (x,IO ())
attachClientTemporary client source =
   do
      (newClient,terminator) <- mkTemporaryClient client
      x <- attachClient newClient source
      return (x,terminator)

-- | mkTemporaryClient is used to map the client by attachClientTemporary.
mkTemporaryClient :: Client d -> IO (Client d,IO ())
mkTemporaryClient client =
   do
      ioRef <- newIORef True -- write False to this to stop the client.
      let
         newClient client = Client (newClientFn client)

         newClientFn (Client oldClientFn) d  =
            do
               goAhead <- readIORef ioRef
               if goAhead
                  then
                     do
                        newClientOpt <- oldClientFn d
                        return (fmap newClient newClientOpt)
                  else
                     return Nothing
      return (newClient client,writeIORef ioRef False)

-- | mkComputedClient computes a client using a value to be supplied via the
-- returned function.  (Hopefully soon after, because of course the source
-- will block until it is.)
mkComputedClient :: (x -> Client d) -> IO (Client d,x -> IO ())
mkComputedClient getClient =
   do
      mVar <- newEmptyMVar
      let
         client = Client clientFn

         clientFn d =
            do
               x <- takeMVar mVar
               let
                  (Client realClientFn) = getClient x
               realClientFn d
      return (client,putMVar mVar)

-- | mkComputedClient is like mkComputedClient, but still more dangerously
-- allows an IO action to compute the client.
--
-- It also allows the supplied function to provide Nothing, indicating no
-- client.
mkComputedClientIO :: (x -> IO (Maybe (Client d))) -> IO (Client d,x -> IO ())
mkComputedClientIO getClient =
   do
      mVar <- newEmptyMVar
      let
         client = Client clientFn

         clientFn d =
            do
               x <- takeMVar mVar
               clientOpt <- getClient x
               case clientOpt of
                  Nothing -> return Nothing
                  Just (Client realClientFn) -> realClientFn d
      return (client,putMVar mVar)

-- | mkStaticClient is used by various functions to create from a client
-- a single static client which tracks its state using an MVar.
mkStaticClient :: Client d -> IO (Client d)
mkStaticClient client =
   do
      (newClient,_) <- mkStaticClientGeneral client
      return newClient

-- | mkStaticClientGeneral is like mkStaticClient except that it also returns
-- an action which determines if the client is still running.
mkStaticClientGeneral :: Client d -> IO (Client d,IO Bool)
mkStaticClientGeneral (client :: Client d) =
   do
      mVar <- newMVar (Just client)
      let
         client = Client clientFn

         clientFn d =
            do
               clientOpt <- takeMVar mVar
               case clientOpt of
                  Nothing -> do
                     putMVar mVar clientOpt
                     return Nothing
                  Just (Client clientFnInner) ->
                     do
                        newClientOpt <- clientFnInner d
                        putMVar mVar newClientOpt
                        return (Just client)

         clientRunning =
            do
               clientOpt <- readMVar mVar
               return (isJust clientOpt)

      return (client,clientRunning)

-- -----------------------------------------------------------------
-- Transformers
-- -----------------------------------------------------------------

map1 :: (x1 -> x2) -> Source x1 d -> Source x2 d
map1 mapFn (Source addClient1) =
   let
      addClient2 d =
         do
            x1 <- addClient1 d
            return (mapFn x1)
   in
      Source addClient2

map1IO :: (x1 -> IO x2) -> Source x1 d -> Source x2 d
map1IO mapFn (Source addClient1) =
   let
      addClient2 d =
         do
            x1 <- addClient1 d
            mapFn x1
   in
      Source addClient2

map2 :: (d1 -> d2) -> Source x d1 -> Source x d2
map2 mapFn (Source addClient1) =
   let
      addClient2 newClient1 = addClient1 (coMapClient mapFn newClient1)
   in
      Source addClient2

coMapClient :: (d1 -> d2) -> Client d2 -> Client d1
coMapClient mapFn (Client clientFn2) =
   let
      client1 = Client clientFn1

      clientFn1 d1 =
         do
            let
               d2 = mapFn d1
            newClient2Opt <- clientFn2 d2
            return (fmap
               (coMapClient mapFn)
               newClient2Opt
               )
   in
      client1

filter2 :: (d1 -> Maybe d2) -> Source x d1 -> Source x d2
filter2 filterFn (Source addClient1) =
   let
      addClient2 newClient1 = addClient1 (filterClient filterFn newClient1)
   in
      Source addClient2

filterClient :: (d1 -> Maybe d2) -> Client d2 -> Client d1
filterClient filterFn (Client clientFn2) =
   let
      client1 = Client clientFn1

      clientFn1 d1 =
         let
            d2Opt = filterFn d1
         in
            case d2Opt of
               Nothing -> return (Just client1)
               Just d2 ->
                  do
                     newClient2Opt <- clientFn2 d2
                     return (fmap
                        (filterClient filterFn)
                        newClient2Opt
                        )
   in
      client1

filter2IO :: (d1 -> IO (Maybe d2)) -> Source x d1 -> Source x d2
filter2IO filterFn (Source addClient1) =
   let
      addClient2 newClient1 = addClient1 (filterClientIO filterFn newClient1)
   in
      Source addClient2

filterClientIO :: (d1 -> IO (Maybe d2)) -> Client d2 -> Client d1
filterClientIO filterFn (Client clientFn2) =
   let
      client1 = Client clientFn1

      clientFn1 d1 =
         do
            d2Opt <- filterFn d1
            case d2Opt of
               Nothing -> return (Just client1)
               Just d2 ->
                  do
                     newClient2Opt <- clientFn2 d2
                     return (fmap
                        (filterClientIO filterFn)
                        newClient2Opt
                        )
   in
      client1

foldSource :: (x -> state) -> (state -> d1 -> (state,d2))
   -> Source x d1 -> Source (state,x) d2
foldSource xFn foldFn =
   let
      xFnIO x = return (xFn x,x)
      foldFnIO state d = return (foldFn state d)
   in
      foldSourceIO xFnIO foldFnIO

-- | Fold a Source so that it can carry state around.
foldSourceIO :: (x1 -> IO (state,x2)) -> (state -> d1 -> IO (state,d2))
   -> Source x1 d1 -> Source (state,x2) d2
foldSourceIO (xFnIO :: x1 -> IO (state,x2))
      (foldFnIO :: state -> d1 -> IO (state,d2))
      ((Source addClient1) :: Source x1 d1) =
   let
      addClient2 :: Client d2 -> IO (state,x2)
      addClient2 client2 =
         do
            let
               createClient :: state -> Client d1
               createClient state = foldClientIO state foldFnIO client2
            (computedClient,writeState) <- mkComputedClient createClient
            x1 <- addClient1 computedClient

            (state,x2) <- xFnIO x1
            writeState state
            return (state,x2)
   in
      Source addClient2

foldClientIO
   :: state -> (state -> d1 -> IO (state,d2)) -> Client d2 -> Client d1
foldClientIO state1 foldFnIO (Client clientFn2) =
   let
      clientFn1 d1 =
         do
            (state2,d2) <- foldFnIO state1 d1
            (newClient2Opt) <- clientFn2 d2
            return (fmap
               (foldClientIO state2 foldFnIO)
               newClient2Opt
               )
   in
      Client clientFn1

stepSource :: (x -> d2) -> (d1 -> d2) -> Source x d1 -> Source x d2
stepSource fromX fromD (Source addClient1) =
   let
      addClient2 (Client clientFn2) =
         do
            (computedClient,writeClientOpt) <- mkComputedClientIO return
            x <- addClient1 ((coMapClient fromD) computedClient)
            clientOpt <- clientFn2 (fromX x)
            writeClientOpt clientOpt
            return x
   in
      Source addClient2

-- | A Source combinator which \"flattens\" lists of updates.
flattenSource :: Source x [d] -> Source x d
flattenSource (Source addClient1) =
   let
      addClient2 client1 = addClient1 (flattenClient client1)
   in
      (Source addClient2)

flattenClient :: Client d -> Client [d]
flattenClient client0 = Client (mkClientFn client0)
   where
      mkClientFn :: Client d -> [d] -> IO (Maybe (Client [d]))
      mkClientFn client0 [] = return (Just (flattenClient client0))
      mkClientFn (Client clientFn1) (d:ds) =
         do
            client1Opt <- clientFn1 d
            case client1Opt of
               Nothing -> return Nothing
               Just client2 -> mkClientFn client2 ds

-- -----------------------------------------------------------------
-- Combinators
-- -----------------------------------------------------------------

-- Combinators
choose :: Source x1 d1 -> Source x2 d2 -> Source (x1,x2) (Either d1 d2)
choose ((Source addClient1) :: Source x1 d1)
       ((Source addClient2) :: Source x2 d2) =
   let
      addClient (client :: Client (Either d1 d2)) =
         do
            (Client staticClientFn) <- mkStaticClient client
            let
               client1 = Client clientFn1

               clientFn1 d1 =
                  do
                     continue <- staticClientFn (Left d1)
                     return (fmap (\ _ -> client1) continue)

               client2 = Client clientFn2

               clientFn2 d2 =
                  do
                     continue <- staticClientFn (Right d2)
                     return (fmap (\ _ -> client2) continue)

            x1 <- addClient1 client1
            x2 <- addClient2 client2
            return (x1,x2)
   in
      Source addClient

seqSource :: Source x1 x1 -> (x1 -> Source x2 x2) -> Source x2 x2
seqSource source getSource = seqSourceIO source (\ x1 -> return (getSource x1))

seqSourceIO :: Source x1 x1 -> (x1 -> (IO (Source x2 x2))) -> Source x2 x2
seqSourceIO (source1 :: Source x1 x1) (getSource2 :: x1 -> IO (Source x2 x2)) =
   let
      addClient client2 =
         do
            (staticClient2 @ (Client staticClientFn),clientRunning)
               <- mkStaticClientGeneral client2

            let
               getClient1 :: (IO (),x1) -> Client x1
               getClient1 (oldTerminator,x1) =
                  let
                     client1 terminator = Client (clientFn1 terminator)

                     clientFn1 oldTerminator x1 =
                        do
                           source2 <- getSource2 x1

                           oldTerminator
                           continue <- clientRunning
                           if continue
                              then
                                 do
                                    (staticClient2',write)
                                       <- mkComputedClient
                                          (const staticClient2)

                                    (x2,newTerminator)
                                       <- attachClientTemporary
                                             staticClient2' source2
                                    staticClientFn x2
                                    write ()
                                    return (Just (client1 newTerminator))
                              else
                                 return Nothing
                  in
                     client1 oldTerminator

            (client1',write) <- mkComputedClient getClient1
            x1 <- attachClient client1' source1

            source2 <- getSource2 x1

            (x2,firstTerminator) <- attachClientTemporary staticClient2 source2
            write (firstTerminator,x1)
            return x2
   in
      Source addClient

-- -----------------------------------------------------------------
-- SimpleSource
-- -----------------------------------------------------------------

newtype SimpleSource x = SimpleSource (Source x x)

staticSimpleSource :: x -> SimpleSource x
staticSimpleSource x = SimpleSource (staticSource x)

staticSimpleSourceIO :: IO x -> SimpleSource x
staticSimpleSourceIO act = SimpleSource (staticSourceIO act)

instance Functor SimpleSource where
   fmap mapFn (SimpleSource source) =
      SimpleSource ( (map1 mapFn) . (map2 mapFn) $ source)

instance HasMapIO SimpleSource where
   mapIO mapFn (SimpleSource source) =
      SimpleSource (
         (map1IO mapFn)
         . (filter2IO
            (\ x ->
               do
                  y <- mapFn x
                  return (Just y)
               )
            )
         $ source
         )


mapIOSeq :: SimpleSource a -> (a -> IO (SimpleSource b)) -> SimpleSource b
mapIOSeq (SimpleSource (source1 :: Source a a))
      (getSimpleSource :: (a -> IO (SimpleSource b))) =
   let
      getSource :: a -> IO (Source b b)
      getSource a =
         do
            (SimpleSource source) <- getSimpleSource a
            return source

      source2 :: Source b b
      source2 = seqSourceIO source1 getSource
   in
      SimpleSource source2

instance Monad SimpleSource where
   return x = SimpleSource (staticSource x)
   (>>=) (SimpleSource source1) getSimpleSource2 =
      let
         getSource2 x =
            let
               (SimpleSource source2) = getSimpleSource2 x
            in
               source2
      in
         SimpleSource (seqSource source1 getSource2)

-- -----------------------------------------------------------------
-- The HasSource and HasSimpleSource classes and their instances
-- -----------------------------------------------------------------

class HasSource hasSource x d | hasSource -> x,hasSource -> d where
   toSource :: hasSource -> Source x d

class HasSimpleSource hasSource x | hasSource -> x where
   toSimpleSource :: hasSource -> SimpleSource x

instance HasSource (Source x d) x d where
   toSource source = source

instance HasSimpleSource (SimpleSource x) x where
   toSimpleSource simpleSource = simpleSource

instance HasSource (SimpleSource x) x x where
   toSource (SimpleSource source) = source

-- -----------------------------------------------------------------
-- The readContents function
-- -----------------------------------------------------------------

-- | Get the current contents of the source, but don\'t specify any other
-- action.
readContents :: HasSource source x d => source -> IO x
readContents hasSource =
   let
      trivialClient = Client (\ _ -> return Nothing)
   in
      attachClient trivialClient (toSource hasSource)

-- -----------------------------------------------------------------
-- Instance of CanAddSinks
-- -----------------------------------------------------------------

instance HasSource hasSource x d => CanAddSinks hasSource x d where
   addOldSink hasSource sink =
      do
         let
            client = Client clientFn

            clientFn d =
               do
                  continue <- putSink sink d
                  return (if continue
                     then
                        Just client
                     else
                        Nothing
                     )
         attachClient client (toSource hasSource)

-- -----------------------------------------------------------------
-- Other handy utilities
-- -----------------------------------------------------------------

-- | Pair two SimpleSource\'s.  This is probably better than using >>=, since it
-- does not require reregistering with the second SimpleSource
pairSimpleSources :: SimpleSource x1 -> SimpleSource x2 -> SimpleSource (x1,x2)
pairSimpleSources (SimpleSource source1) (SimpleSource source2) =
   let
      sourceChoose = choose source1 source2
      source =
         foldSource
            id
            (\ (x1,x2) change ->
               let
                  new = case change of
                     Left newX1 -> (newX1,x2)
                     Right newX2 -> (x1,newX2)
               in
                  (new,new)
               )
            sourceChoose
   in
      SimpleSource (map1 fst source)

-- | Does a similar job to pairSimpleSources, so that the sources run
-- parallel.
sequenceSimpleSource :: [SimpleSource x] -> SimpleSource [x]
sequenceSimpleSource [] = return []
sequenceSimpleSource (first:rest) =
   fmap (uncurry (:)) (pairSimpleSources first (sequenceSimpleSource rest))

-- | For each update d, pairs it with its predecessor (given first).
-- For the very first update, a value is given based on the initial x,
-- mapped by the given function.
mkHistorySource :: (x -> d) -> Source x d -> Source x (d,d)
mkHistorySource getD source =
   map1 (\ (d,x) -> x) (foldSource getD (\ lastD d -> (d,(lastD,d))) source)

-- | Like mkHistorySource but for SimpleSource\'s; the x returns the initial
-- value to compare with.
mkHistorySimpleSource :: x -> SimpleSource x -> SimpleSource (x,x)
mkHistorySimpleSource lastX (SimpleSource source) =
   SimpleSource (map1 (\ x -> (lastX,x)) (mkHistorySource id source))

-- | filter out consecutive duplicates
uniqSimpleSource :: Eq x => SimpleSource x -> SimpleSource x
uniqSimpleSource (SimpleSource source0) =
   let
      source1 = mkHistorySource id source0
      source2 = filter2 (\ (lastD,d) -> if lastD == d then Nothing else Just d)
         source1
   in
      SimpleSource source2


-- | Fold a Simple Source, so that it carries state.
-- The state is recomputed for each client.
foldSimpleSourceIO :: (x1 -> IO (state,x2)) -> (state -> x1 -> IO (state,x2))
   -> SimpleSource x1 -> SimpleSource x2
foldSimpleSourceIO (getStateIO :: x1 -> IO (state,x2)) updateStateIO
      (SimpleSource (source :: Source x1 x1)) =
   let
      source1 :: Source (state,x2) x2
      source1 = foldSourceIO getStateIO updateStateIO source
   in
      SimpleSource (map1 snd source1)

-- | replaces the first value of the SimpleSource.
change1 :: SimpleSource x -> x -> SimpleSource x
change1 (SimpleSource source) x = SimpleSource (map1 (\ _ -> x) source)

-- | Run the specified actions for the source, using the given SinkID and
-- in the ParallelExec thread.
-- The x -> IO () action is guaranteed to be performed before any of the
-- d -> IO () actions.
addNewSourceActions :: Source x d -> (x -> IO ()) -> (d -> IO ())
   -> SinkID -> ParallelExec -> IO x
addNewSourceActions (source1 :: Source x d) actionX actionD sinkID parallelX =
   do
      mVar <- newEmptyMVar -- used to return the first x value
      let
         actionX' x =
            do
               putMVar mVar x
               actionX x

         (source2 :: Source x (IO ())) = stepSource actionX' actionD source1
      addNewQuickSinkGeneral
         source2
         (\ action -> parallelExec parallelX action)
         sinkID
      takeMVar mVar

-- -----------------------------------------------------------------
-- Trace functions
-- -----------------------------------------------------------------

-- | Outputs information about what comes through the source, turning
-- it into a String with the supplied function.  (This is done once
-- for each active client.)
traceSimpleSource :: (a -> String) -> SimpleSource a -> SimpleSource a
traceSimpleSource toS (SimpleSource source) =
   SimpleSource (
      (map1IO
         (\ a ->
            do
               putStrLn ("Initialising "++toS a)
               return a
            )
         )
      .
      (filter2IO
         (\ a ->
            do
               putStrLn ("Updating "++toS a)
               return (Just a)
            )
         )
      $
      source
      )

-- | Outputs information about what comes through the source, turning
-- it into a String with the supplied function.  (This is done once
-- for each active client.)
traceSource :: (a -> String) -> (d -> String) -> Source a d -> Source a d
traceSource toS1 toS2 source =
   (map1IO
      (\ a ->
         do
            putStrLn ("Initialising "++toS1 a)
            return a
         )
      )
   .
   (filter2IO
      (\ d ->
         do
            putStrLn ("Updating "++toS2 d)
            return (Just d)
         )
      )
   $
   source

-- -----------------------------------------------------------------
-- noLoop functions.  (Only noLoopSimpleSource is exported, for now.)
-- -----------------------------------------------------------------

noLoopSource :: TSem -> ([String] -> x) -> ([String] -> d)
   -> Source x d -> Source x d
noLoopSource tSem toX toD (Source addClient0 :: Source x d) =
   let
      mkClient :: Client d -> Client d
      mkClient client = Client (mkClientFn client)

      mkClientFn :: Client d -> d -> IO (Maybe (Client d))
      mkClientFn (client @ (Client clientFn0)) d =
         do
            (looped :: Either [String] (Maybe (Client d)))
               <- synchronizeTSem tSem (clientFn0 d)
            case looped of
               Left strings ->
                  do
                     debug ("mkClientFn loop caught " ++ show strings)
                     -- repeat with the artificial d (which had better
                     -- not cause a loop).
                     mkClientFn client (toD strings)
               Right clientOpt -> return (fmap mkClient clientOpt)

      addClient1 :: Client d -> IO x
      addClient1 client =
         do
            stringsOrX <- synchronizeTSem tSem
               (addClient0 (mkClient client))
            case stringsOrX of
               Left strings -> return (toX strings)
               Right x -> return x
   in
      Source addClient1

-- | Used when we are worried that a SimpleSource recursively constructed
-- by mapIOSeq, >>= and friends may actually try to call itself, and
-- so loop forever.   The Strings identify the SimpleSource,
-- and so the [String] is effectively a backtrace of the TSems, revealing what
-- chain of simple sources might have caused the loop.
noLoopSimpleSource :: TSem -> ([String] -> a) -> SimpleSource a
   -> SimpleSource a
noLoopSimpleSource tSem toA (SimpleSource source0) =
   let
      source1 = noLoopSource tSem toA toA source0
   in
      SimpleSource source1

-- ---------------------------------------------------------------------------
-- mkIOSource and mkIOSimpleSource
-- ---------------------------------------------------------------------------

mkIOSource :: IO (Source x d) -> Source x d
mkIOSource act =
   let
      addClient client =
         do
            (Source addClient1) <- act
            addClient1 client
   in
      Source addClient

mkIOSimpleSource :: IO (SimpleSource a) -> SimpleSource a
mkIOSimpleSource act =
   SimpleSource (mkIOSource (
      do
         simpleSource <- act
         return (toSource simpleSource)
      ))