{-
    Copyright 2016 Markus Ongyerth

    This file is part of Monky.

    Monky is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Monky is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Monky.  If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-|
Module : Monky.Examples.Combine
Description: Combine multiple modules into one (to avoid seperators)
Maintainer: ongy
Stability: testing
Portability: linux

The functions in this module can be used to avoid printing seperators between modules.

Nearly equivalend example (image is missing):

@
pollPack 1 $ getCPUHandle' ScalingCur
pollPack 1 $ (getFreqHandle ScalingCur) `combine` getRawCPU `combine` getTempHandle'
@

The main function exported by this module are:
  * 'combine'
  * 'combineD'
  * 'combineE'
  * 'combineF'

They are mainly overloads of the same concept, for combinations of 'EvtModule' and 'PollModule'

The mixed functions ('combineD' and 'combineF') create a 'PollModule'. The 'EvtModule' will update
a cache in the new module and output will be updated when the 'PollModule' is asked to do so.

NOTE: because of this, the event will be handled when it is detected, but the output will not update
until the 'PollModule' wrapper ticks once.
-}
module Monky.Examples.Combine
  ( CombiHandle
  , combine

  , EvtCombi
  , combineE

  , EPCombi
  , combineF

  , PECombi
  , combineD
  )
where

import Monky.Modules
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar (MVar, withMVar, newMVar)
import Data.IORef (IORef, readIORef, atomicWriteIORef, newIORef)

-- |Wrapper type for 'PollModule's
data (PollModule a, PollModule b) => CombiHandle a b = C a b

instance (PollModule a, PollModule b) => PollModule (CombiHandle a b) where
  getOutput (C a b) = do
    c <- getOutput a
    d <- getOutput b
    return (c ++ d)
  initialize (C a b) = initialize a >> initialize b

-- |Combine two 'PollModule's. The first arguments output will be printed first
combine :: (PollModule a, PollModule b) => IO a -> IO b -> IO (CombiHandle a b)
combine a b = do
  c <- a
  d <- b
  return $ C c d

-- Module handle a, b. IORef for caching a, b. MVar to implement boolean, containing value doesn't matter
-- |Wrapper type for 'EvtModule's
data (EvtModule a, EvtModule b) => EvtCombi a b = EC a b (IORef [MonkyOut]) (IORef [MonkyOut]) (MVar Bool)

instance (EvtModule a, EvtModule b) => EvtModule (EvtCombi a b) where
  startEvtLoop (EC a b r1 r2 m) act = do
    _ <- forkIO $ startEvtLoop a (\out -> withMVar m $ \_ -> do
      atomicWriteIORef r1 out
      other <- readIORef r2
      act (out ++ other))
    startEvtLoop b (\out -> withMVar m $ \_ -> do
      atomicWriteIORef r2 out
      other <- readIORef r1
      act (other ++ out))

-- |Combine two 'EvtModule's. The first argument will be printed first.
combineE :: (EvtModule a, EvtModule b) => IO a -> IO b -> IO (EvtCombi a b)
combineE a b = do
  m1 <- a
  m2 <- b
  r1 <- newIORef []
  r2 <- newIORef []
  m <- newMVar True
  return $ EC m1 m2 r1 r2 m

-- |Wrapper type for mixed combination.
data (EvtModule a, PollModule b) => EPCombi a b = EP a b (IORef [MonkyOut])

instance (EvtModule a, PollModule b) => PollModule (EPCombi a b) where
  initialize (EP a b r) = do
    startEvtLoop a (atomicWriteIORef r)
    initialize b
  getOutput (EP _ b r) = do
    o1 <- readIORef r
    o2 <- getOutput b
    return (o1 ++ o2)

-- |Combine a 'EvtModule' with a 'PollModule'. This will alwasy create a 'PollModule'.
-- |Event updates will not be displayed until the poll modules ticks once.
combineF :: (EvtModule a, PollModule b) => IO a -> IO b -> IO (EPCombi a b)
combineF a b = do
  m1 <- a
  m2 <- b
  r <- newIORef []
  return $ EP m1 m2 r


-- |Wrapper type for mixed combination.
data (PollModule a, EvtModule b) => PECombi a b = PE a b (IORef [MonkyOut])

instance (PollModule a, EvtModule b) => PollModule (PECombi a b) where
  initialize (PE a b r) = do
    startEvtLoop b (atomicWriteIORef r)
    initialize a
  getOutput (PE a _ r) = do
    o1 <- getOutput a
    o2 <- readIORef r
    return (o1 ++ o2)

-- |Look at 'combineF' for documentation
combineD :: (PollModule a, EvtModule b) => IO a -> IO b -> IO (PECombi a b)
combineD a b = do
  m1 <- a
  m2 <- b
  r <- newIORef []
  return $ PE m1 m2 r