{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}

module LiveCoding.Debugger where

-- base
import Control.Concurrent
import Control.Monad (void)
import Data.Data
import Data.IORef

-- transformers
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State

-- syb
import Data.Generics.Text

-- essence-of-live-coding
import LiveCoding.LiveProgram
import LiveCoding.Cell


\subsection{Debugging the live state}
Having the complete state of the program in one place allows us to inspect and debug it in a central place.
We might want to interact with the user,
display aspects of the state
and possibly even change it in place.
In short, a debugger is a program that can read and modify,
as an additional effect,
the state of an arbitrary live program:
newtype Debugger m = Debugger
  { getDebugger :: forall s .
      Data s => LiveProgram (StateT s m)
-- Standalone deriving isn't clever enough to handle the existential type
instance Monad m => Semigroup (Debugger m) where
  debugger1 <> debugger2 = Debugger $ getDebugger debugger1 <> getDebugger debugger2

instance Monad m => Monoid (Debugger m) where
  mempty = Debugger mempty

getC :: Monad m => Cell (StateT s m) a s
getC = constM get

putC :: Monad m => Cell (StateT s m) s ()
putC = arrM put
A simple debugger prints the unmodified state to the console:
gshowDebugger :: Debugger IO
gshowDebugger = Debugger
  $ liveCell $ arrM $ const $ do
    state <- get
    lift $ putStrLn $ gshow state
Thanks to the \mintinline{haskell}{Data} typeclass,
the state does not need to be an instance of \mintinline{haskell}{Show} for this to work:
\texttt{syb} offers a generic \mintinline{haskell}{gshow} function.
A more sophisticated debugger could connect to a GUI and display the state there,
even offering the user to pause the execution and edit the state live.
\fxwarning{Should I explain countDebugger? What for?}
\fxerror{Following a comment on cat theory. Add to appendix?}
Debuggers are endomorphisms in the Kleisli category of \mintinline{haskell}{IO},
and thus \mintinline{haskell}{Monoid}s:
A pair of them can be chained by executing them sequentially,
and the trivial debugger purely \mintinline{haskell}{return}s the state unchanged.
We can bake a debugger into a live program:
  :: Monad       m
  => LiveProgram m
  -> Debugger    m
  -> LiveProgram m
withDebugger = (liveCell .) . withDebuggerC . toLiveCell

  :: Monad    m
  => Cell     m a b
  -> Debugger m
  -> Cell     m a b
withDebuggerC (Cell state step) (Debugger (LiveProgram dbgState dbgStep)) = Cell { .. }
    cellState = Debugging { .. }
    cellStep Debugging { .. } a = do
      (b, state') <- step state a
      states <- runStateT (dbgStep dbgState) state'
      return (b, uncurry (flip Debugging) states)
Again, let us understand the function through its state type:
data Debugging dbgState state = Debugging
  { state    :: state
  , dbgState :: dbgState
  } deriving (Data, Eq, Show)
On every step, the debugger becomes active after the cell steps,
and is fed the current \mintinline{haskell}{state} of the main program.
Depending on \mintinline{haskell}{dbgState},
it may execute some side effects or mutate the \mintinline{haskell}{state},
or do nothing at all\footnote{%
This option is important for performance: E.g. for an audio application,
a side effect on every sample can slow down unbearably.}.

Live programs with debuggers are started just as usual.
\fxwarning{Automatise this and the next output}
Inspecting the state of the example \mintinline{haskell}{printSineWait} from Section \ref{sec:control flow context} is daunting, though:
(Composition ((,) (Composition ((,) (())
(Composition ((,) (()) (Composition ((,)
(Composition ((,) (()) (Composition ((,)
\fxerror{I still have the tuples here!}
The arrow syntax desugaring introduces a lot of irrelevant overhead such as compositions with the trivial state type \mintinline{haskell}{()},
hiding the parts of the state we are actually interested in.
Luckily, it is a simple, albeit lengthy exercise in generic programming to prune all irrelevant parts of the state,
resulting in a tidy output%\footnote{%
%Line breaks were added to fit the columns.}
Let us inspect the state of the example \mintinline{haskell}{printSineWait} from Section \ref{sec:control flow context}.
It is a simple, albeit lengthy exercise in generic programming to prune all irrelevant parts of the state when printing it,
resulting in a tidy output like:
\fxwarning{Automatise this}
NotThrown: (1.0e-3)
 >>> +(0.0) >>> (0.0)+ >>> (1)
NotThrown: (2.0e-3)
 >>> +(0.0) >>> (0.0)+ >>> (2)
NotThrown: (2.0009999999998906)
 >>> +(0.0) >>> (0.0)+ >>> (2001)
 >>> +(3.9478417604357436e-3) >>> (0.0)+
 >>> (2002)
 >>> +(7.895683520871487e-3) >>>
 >>> (2003)
The cell is initialised in a state where the exception hasn't been thrown yet,
and the \mintinline{haskell}{localTime} is \mintinline{haskell}{1.0e-3} seconds.
The next line corresponds to the initial state (position and velocity) of the sine generator which will be activated after the exception has been thrown,
followed by the internal counter of \mintinline{haskell}{printEverySecond}.
In the next step, local time and counter have progressed.
Two thousand steps later, the exception is finally thrown,
and the sine wave starts.

newtype CountObserver = CountObserver { observe :: IO Integer }

countDebugger :: IO (Debugger IO, CountObserver)
countDebugger = do
  countRef <- newIORef 0
  observeVar <- newEmptyMVar
  let debugger = Debugger $ liveCell $ arrM $ const $ lift $ do
        n <- readIORef countRef
        putMVar observeVar n
        void $ takeMVar observeVar
        writeIORef countRef $ n + 1
      observer = CountObserver $ yield >> readMVar observeVar
  return (debugger, observer)

await :: CountObserver -> Integer -> IO ()
await CountObserver { .. } nMax = go
  go = do
    n <- observe
    if n > nMax then return () else go