-- |
-- Module:     Control.Wire.Types
-- Copyright:  (c) 2011 Ertugrul Soeylemez
-- License:    BSD3
-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
--
-- Types used in the netwire library.

module Control.Wire.Types
    ( -- * The wire
      Wire(..),

      -- * Smart construction
      mkFix,
      mkGen,
      mkPure,
      mkPureFix,

      -- * Destruction
      toGen
    )
    where

import qualified Control.Exception as Ex
import Control.Applicative
import Control.Arrow
import Control.Arrow.Operations
import Control.Arrow.Transformer
import Control.Category
import Control.Wire.Classes
import Data.Monoid
import Prelude hiding ((.), id)


-- | Signal networks.

data Wire e (>~) a b where
    WGen  :: !(a >~ (Either e b, Wire e (>~) a b)) -> Wire e (>~) a b
    WPure :: !(a -> (Either e b, Wire e (>~) a b)) -> Wire e (>~) a b


-- | Wire side channels.

instance ArrowChoice (>~) => Arrow (Wire e (>~)) where
    arr f = mkPureFix $ Right . f

    first (WGen c) =
        WGen $ proc (x', y) -> do
            (mx, w) <- c -< x'
            returnA -< (fmap (, y) mx, first w)
    first (WPure f) =
        WPure $ \(x', y) ->
            let (mx, w) = f x'
            in (fmap (, y) mx, first w)

    second (WGen c) =
        WGen $ proc (x, y') -> do
            (my, w) <- c -< y'
            returnA -< (fmap (x,) my, second w)
    second (WPure f) =
        WPure $ \(x, y') ->
            let (my, w) = f y'
            in (fmap (x,) my, second w)

    -- (&&&) combinator.
    WGen c1 &&& w2'@(WGen c2) =
        WGen $ proc x' -> do
            (mx1, w1) <- c1 -< x'
            case mx1 of
              Left ex  -> returnA -< (Left ex, w1 &&& w2')
              Right x1 -> do
                  (mx2, w2) <- c2 -< x'
                  returnA -< (fmap (x1,) mx2, w1 &&& w2)

    WGen c1 &&& w2'@(WPure g) =
        WGen $ proc x' -> do
            (mx1, w1) <- c1 -< x'
            case mx1 of
              Left ex  -> returnA -< (Left ex, w1 &&& w2')
              Right x1 ->
                  let (mx2, w2) = g x' in
                  returnA -< (fmap (x1,) mx2, w1 &&& w2)

    WPure f &&& w2'@(WGen c2) =
        WGen $ proc x' ->
            let (mx1, w1) = f x' in
            case mx1 of
              Left ex  -> returnA -< (Left ex, w1 &&& w2')
              Right x1 -> do
                  (mx2, w2) <- c2 -< x'
                  returnA -< (fmap (x1,) mx2, w1 &&& w2)

    WPure f &&& w2'@(WPure g) =
        WPure $ \x' ->
            let (mx1, w1) = f x'
                (mx2, w2) = g x' in
            case mx1 of
              Left ex  -> (Left ex, w1 &&& w2')
              Right x1 -> (fmap (x1,) mx2, w1 &&& w2)

    -- (***) combinator.
    WGen c1 *** w2'@(WGen c2) =
        WGen $ proc (x', y') -> do
            (mx, w1) <- c1 -< x'
            case mx of
              Left ex -> returnA -< (Left ex, w1 *** w2')
              Right x -> do
                  (my, w2) <- c2 -< y'
                  returnA -< (fmap (x,) my, w1 *** w2)

    WGen c1 *** w2'@(WPure g) =
        WGen $ proc (x', g -> (my, w2)) -> do
            (mx, w1) <- c1 -< x'
            case mx of
              Left ex -> returnA -< (Left ex, w1 *** w2')
              Right x -> returnA -< (fmap (x,) my, w1 *** w2)

    WPure f *** w2'@(WGen c2) =
        WGen $ proc (f -> (mx, w1), y') -> do
            case mx of
              Left ex -> returnA -< (Left ex, w1 *** w2')
              Right x -> do
                  (my, w2) <- c2 -< y'
                  returnA -< (fmap (x,) my, w1 *** w2)

    WPure f *** w2'@(WPure g) =
        WPure $ \(f -> (mx, w1), g -> (my, w2)) ->
            case mx of
              Left ex -> (Left ex, w1 *** w2')
              Right x -> (fmap (x,) my, w1 *** w2)


-- | Support for choice (signal redirection).

instance ArrowChoice (>~) => ArrowChoice (Wire e (>~)) where
    left w'@(WPure f) =
        WPure $ \mx' ->
            case mx' of
              Left x'  -> fmap Left *** left $ f x'
              Right x' -> (Right (Right x'), left w')

    left w'@(WGen c) =
        WGen $ proc mx' ->
            case mx' of
              Left x'  -> (fmap Left *** left) ^<< c -< x'
              Right x' -> returnA -< (Right (Right x'), left w')

    right w'@(WPure f) =
        WPure $ \mx' ->
            case mx' of
              Right x' -> fmap Right *** right $ f x'
              Left x'  -> (Right (Left x'), right w')

    right w'@(WGen c) =
        WGen $ proc mx' ->
            case mx' of
              Right x' -> (fmap Right *** right) ^<< c -< x'
              Left x'  -> returnA -< (Right (Left x'), right w')

    wl'@(WPure f) +++ wr'@(WPure g) =
        WPure $ \mx' ->
            case mx' of
              Left x'  -> (fmap Left  *** (+++ wr')) . f $ x'
              Right x' -> (fmap Right *** (wl' +++)) . g $ x'

    wl' +++ wr' =
        WGen $ proc mx' ->
            case mx' of
              Left x'  -> arr (fmap Left  *** (+++ wr')) . toGen wl' -< x'
              Right x' -> arr (fmap Right *** (wl' +++)) . toGen wr' -< x'

    wl'@(WPure f) ||| wr'@(WPure g) =
        WPure $ \mx' ->
            case mx' of
              Left x'  -> second (||| wr') . f $ x'
              Right x' -> second (wl' |||) . g $ x'

    wl' ||| wr' =
        WGen $ proc mx' ->
            case mx' of
              Left x'  -> arr (second (||| wr')) . toGen wl' -< x'
              Right x' -> arr (second (wl' |||)) . toGen wr' -< x'


-- | Support for one-instant delays.

instance (ArrowChoice (>~), ArrowLoop (>~)) => ArrowCircuit (Wire e (>~)) where
    delay x' = mkPure $ \x -> (Right x', delay x)


-- | Inhibition handling interface.  See also the
-- "Control.Wire.Trans.Exhibit" and "Control.Wire.Prefab.Event" modules.

instance ArrowChoice (>~) => ArrowError e (Wire e (>~)) where
    raise = mkPureFix Left

    handle (WPure f) wh'@(WPure fh) =
        WPure $ \x' ->
            let (mx, w) = f x' in
            case mx of
              Left ex ->
                  let (mxh, wh) = fh (x', ex)
                  in (mxh, handle w wh)
              Right _ -> (mx, handle w wh')

    handle w' wh' =
        WGen $ proc x' -> do
            (mx, w) <- toGen w' -< x'
            case mx of
              Left ex -> do
                  (mxh, wh) <- toGen wh' -< (x', ex)
                  returnA -< (mxh, handle w wh)
              Right _ -> returnA -< (mx, handle w wh')

    newError (WPure f) = WPure $ (Right *** newError) . f
    newError (WGen c) = WGen $ arr (Right *** newError) . c

    tryInUnless (WPure f) ws'@(WPure fs) we'@(WPure fe) =
        WPure $ \x' ->
            let (mx, w) = f x' in
            case mx of
              Left ex ->
                  let (mxe, we) = fe (x', ex)
                  in (mxe, tryInUnless w ws' we)
              Right x ->
                  let (mxs, ws) = fs (x', x)
                  in (mxs, tryInUnless w ws we')

    tryInUnless w' ws' we' =
        WGen $ proc x' -> do
            (mx, w) <- toGen w' -< x'
            case mx of
              Left ex -> do
                  (mxe, we) <- toGen we' -< (x', ex)
                  returnA -< (mxe, tryInUnless w ws' we)
              Right x -> do
                  (mxs, ws) <- toGen ws' -< (x', x)
                  returnA -< (mxs, tryInUnless w ws we')


-- | When the target arrow is an 'ArrowIO' (e.g. a Kleisli arrow over
-- IO), then the wire arrow is also an @ArrowIO@.

instance (Applicative f, ArrowChoice (>~), ArrowIO (>~)) =>
         ArrowIO (Wire (f Ex.SomeException) (>~)) where
    arrIO = mkFix $ arr (mapLeft pure) <<< arrIO <<< arr Ex.try


-- | Value recursion in the wire arrows.  **NOTE**: Wires with feedback
-- must *never* inhibit.  There is an inherent, fundamental problem with
-- handling the inhibition case, which you will observe as a fatal
-- pattern match error.

instance (ArrowChoice (>~), ArrowLoop (>~)) => ArrowLoop (Wire e (>~)) where
    loop w' =
        WGen $ proc x' -> do
            rec (Right (x, d), w) <- toGen w' -< (x', d)
            returnA -< (Right x, loop w)


-- | Combining possibly inhibiting wires.

instance (ArrowChoice (>~), Monoid e) => ArrowPlus (Wire e (>~)) where
    WGen c1 <+> w2'@(WGen c2) =
        WGen $ proc x' -> do
            (mx1, w1) <- c1 -< x'
            case mx1 of
              Right _ -> returnA -< (mx1, w1 <+> w2')
              Left ex1 -> do
                  (mx2, w2) <- c2 -< x'
                  returnA -< (mapLeft (mappend ex1) mx2, w1 <+> w2)

    WGen c1 <+> w2'@(WPure g) =
        WGen $ proc x' -> do
            (mx1, w1) <- c1 -< x'
            case mx1 of
              Right _ -> returnA -< (mx1, w1 <+> w2')
              Left ex1 ->
                  let (mx2, w2) = g x' in
                  returnA -< (mapLeft (mappend ex1) mx2, w1 <+> w2)

    WPure f <+> w2'@(WGen c2) =
        WGen $ proc x' ->
            let (mx1, w1) = f x' in
            case mx1 of
              Right _ -> returnA -< (mx1, w1 <+> w2')
              Left ex1 -> do
                  (mx2, w2) <- c2 -< x'
                  returnA -< (mapLeft (mappend ex1) mx2, w1 <+> w2)

    WPure f <+> w2'@(WPure g) =
        WPure $ \x' ->
            let (mx1, w1) = f x'
                (mx2, w2) = g x' in
            case mx1 of
              Right _  -> (mx1, w1 <+> w2')
              Left ex1 -> (mapLeft (mappend ex1) mx2, w1 <+> w2)


-- | If the underlying arrow is a reader arrow, then the wire arrow is
-- also a reader arrow.

instance (ArrowChoice (>~), ArrowReader r (>~)) => ArrowReader r (Wire e (>~)) where
    readState = lift readState

    newReader (WPure f) = WPure (second newReader . f . fst)
    newReader (WGen c)  = WGen $ arr (second newReader) . newReader c


-- | If the underlying arrow is a state arrow, then the wire arrow is
-- also a state arrow.

instance (ArrowChoice (>~), ArrowState s (>~)) => ArrowState s (Wire e (>~)) where
    fetch = lift fetch
    store = lift store


-- | Wire arrows are arrow transformers.

instance ArrowChoice (>~) => ArrowTransformer (Wire e) (>~) where
    lift c = mkFix $ Right ^<< c


-- | If the underlying arrow is a writer arrow, then the wire arrow is
-- also a writer arrow.

instance (ArrowChoice (>~), ArrowWriter w (>~)) => ArrowWriter w (Wire e (>~)) where
    write = lift write

    newWriter (WPure f) = WPure ((fmap (, mempty) *** newWriter) . f)
    newWriter (WGen c) =
        WGen $ arr (\((mx, w), log) ->
                        (fmap (, log) mx, newWriter w)) .
               newWriter c


-- | The always inhibiting wire.  The @zeroArrow@ is equivalent to
-- "Control.Wire.Prefab.Event.never".

instance (ArrowChoice (>~), Monoid e) => ArrowZero (Wire e (>~)) where
    zeroArrow = mkPureFix (const $ Left mempty)


-- | Sequencing of wires.

instance ArrowChoice (>~) => Category (Wire e (>~)) where
    id = arr id

    w2'@(WGen c2) . WGen c1 =
        WGen $ proc x'' -> do
            (mx', w1) <- c1 -< x''
            case mx' of
              Left ex  -> returnA -< (Left ex, w2' . w1)
              Right x' -> do
                  (mx, w2) <- c2 -< x'
                  returnA -< (mx, w2 . w1)

    w2'@(WGen c2) . WPure g =
        WGen $ proc (g -> (mx', w1)) -> do
            case mx' of
              Left ex  -> returnA -< (Left ex, w2' . w1)
              Right x' -> do
                  (mx, w2) <- c2 -< x'
                  returnA -< (mx, w2 . w1)

    w2'@(WPure f) . WGen c1 =
        WGen $ proc x'' -> do
            (mx', w1) <- c1 -< x''
            case mx' of
              Left ex               -> returnA -< (Left ex, w2' . w1)
              Right (f -> (mx, w2)) -> returnA -< (mx, w2 . w1)

    w2'@(WPure f) . WPure g =
        WPure $ \(g -> (mx', w1)) ->
            case mx' of
              Left ex               -> (Left ex, w2' . w1)
              Right (f -> (mx, w2)) -> (mx, w2 . w1)


-- | Maps over the left side of an 'Either' value.

mapLeft :: (e' -> e) -> Either e' a -> Either e a
mapLeft f (Left x)  = Left (f x)
mapLeft _ (Right x) = Right x


-- | Create a wire from the given stateless transformation computation.

mkFix :: Arrow (>~) => (a >~ Either e b) -> Wire e (>~) a b
mkFix c = let w = WGen (arr (, w) . c) in w


-- | Create a wire from the given transformation computation.

mkGen :: (a >~ (Either e b, Wire e (>~) a b)) -> Wire e (>~) a b
mkGen = WGen


-- | Create a pure wire from the given transformation function.

mkPure :: (a -> (Either e b, Wire e (>~) a b)) -> Wire e (>~) a b
mkPure = WPure


-- | Create a pure wire from the given transformation function.

mkPureFix :: (a -> Either e b) -> Wire e (>~) a b
mkPureFix f = let w = WPure ((, w) . f) in w


-- | Convert the given wire to a generic arrow computation.

toGen :: Arrow (>~) => Wire e (>~) a b -> (a >~ (Either e b, Wire e (>~) a b))
toGen (WGen c)  = c
toGen (WPure f) = arr f