-- |
--   Module:     Control.GUI
--   Copyright:  (c) 2015 Schell Scivally
--   License:    MIT
--   Maintainer: Schell Scivally <efsubenovex@gmail.com>
--
--   Graphical user interfaces that are renderable, change over time and
--   eventually produce a value.
--
--   GUIs are comprised of event streams and renderable datatypes that change
--   over time. A GUI is a monadic layer on top of automaton varying values
--   provided by the 'varying' library.

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Control.GUI (
    -- * Definition
    UX(..),
    GUI(..),
    -- * Creation
    -- $creation
    gui,
    -- * Transformation
    -- $transformation
    transformGUI,
    -- * Combination
    -- $combination
    combineGUI
) where

import Control.Varying
import Control.Monad.IO.Class
import Control.Arrow (first)
import Data.Renderable
import Data.Monoid

--------------------------------------------------------------------------------
-- Defining a GUI
--------------------------------------------------------------------------------
-- | A discrete step in a "user experience". This is simply a type that
-- discretely describes an eventual value on the right and a renderable datatype
-- on the left. It is assumed that the left value is a datatype
-- that represents a user inteface and that the user is interacting with it
-- to eventually produce the datatype on the right.
data UX a b = UX a (Event b)

-- | A UX is a functor by applying a function to the contained event's value.
instance Functor (UX a) where
    fmap f (UX a b) = UX a $ fmap f b

-- | A UX is an applicative if its left datatype is a monoid. It replies to
-- 'pure' with an empty left value while the right value is the argument
-- wrapped in an event. It means "the argument happens instantly with no
-- user interface".
instance Monoid a => Applicative (UX a) where
    pure a = UX mempty $ Event a
    (UX uia f) <*> (UX uib b) = UX (uia <> uib) (f <*> b)

-- | A UX is renderable if its left value is also renderable. It inherits all
-- Renderable type variables from its left value and simply renders that
-- value.
instance Composite a m r t => Composite (UX a b) m r t where
    composite (UX ui _) = composite ui

-- | A GUI is a UX that varies over some domain. What this means is that a
-- graphical user interface is essentially a user experience that eventually
-- produces a value. 'm' is the underlying monad. 'i' is the type of the user
-- input.  'a' is the renderable type - the interface itself. 'b' is the
-- eventual produced value.
newtype GUI m i a b = GUI { runGUI :: Var m i (UX a b) }

-- | A GUI can be a monoid if its UX\'s left and right types are monoids.
-- The identity is a GUI that has no user interface and immediately
-- produces an event who\'s value is the identity of its UX's right type.
-- The associative operation is to combine the two GUIs with 'combineGUI'.
instance (Monad m, Monoid a, Monoid b) => Monoid (GUI m i a b) where
    mempty = pure mempty
    mappend g h = combineGUI g h mappend

-- | A GUI is a functor by applying a function to the eventual produced
-- value.
instance Monad m => Functor (GUI m i a) where
    fmap f (GUI v) = GUI $ fmap (fmap f) v

-- | A GUI is applicative if its UX\'s left value is a monoid. It responds
-- to 'pure' by returning a GUI that has no user interface and immediately
-- produces the argument. It responds to '<*>' by applying the left
-- argument to the right. Each side\'s left UX value will be 'mappend' \'d.
instance (Monad m, Monoid a) => Applicative (GUI m i a) where
    pure = GUI . pure . pure
    (GUI vf) <*> (GUI va) = GUI $ ((<*>) <$> vf) <*> va

-- | A GUI is a monad if its UX's left value is a monoid. It responds to
-- '>>=' by returning a new GUI that runs until it produces a value, then
-- that value is used to create yet another GUI.
instance (Monad m, Monoid a) => Monad (GUI m i a) where
    (GUI v) >>= f = GUI $ Var $ \i -> do
        (UX a e, v') <- runVar v i
        case e of
            NoEvent -> return (UX a NoEvent, runGUI $ GUI v' >>= f)
            Event b -> runVar (runGUI $ f b) i

-- | A GUI can perform IO if its underlying monad can perform IO.
instance (MonadIO m, Monoid a) => MonadIO (GUI m i a) where
    liftIO f = GUI $ Var $ \_ -> do
        a <- liftIO f
        return (UX mempty (Event a), runGUI $ pure a)
--------------------------------------------------------------------------------
-- $creation
-- In order to create a GUI you must first have a datatype that is
-- 'Renderable'. Then you must create a 'varying' value of that datatype.
-- Also needed is an event stream that eventually ends the user's interaction.
-- The idea is that your interface varies over time and/or user input but
-- eventually produces a result value that can be used in a monadic
-- sequence.
--------------------------------------------------------------------------------
-- | Creates a new GUI displaying an interface that eventually produces a value.
-- The type used to represent the user interface must have a 'Decomposable'
-- instance, that way the resulting GUI\'s discrete values can be rendered.
gui :: (Monad m, Composite a m r t)
    => Var m i a
    -- ^ The stream of a changing user interface.
    -> Var m i (Event b)
    -- ^ The event stream that concludes a user\'s interaction. When this
    -- stream produces an event the interaction will end and the merging
    -- function will be used to create the GUI\'s return type.
    -> (a -> b -> c)
    -- ^ The merging function that combines the interface's final value with the
    -- value produced by the event stream.
    -> GUI m i [(t, Element m r t)] c
gui v ve f = GUI $ Var $ \i -> do
    (a, v')  <- runVar v i
    (e, ve') <- runVar ve i
    let ui = composite a
    case e of
        NoEvent -> return (UX ui NoEvent, runGUI $ gui v' ve' f)
        Event b -> return (UX ui (Event $ f a b), pure $ UX [] $ Event $ f a b)
--------------------------------------------------------------------------------
-- $transformation
-- Simply put - here we are applying some kind of transformation to your
-- renderable interface. This most likely a standard two or three dimensional
-- affine transformation. Since the transformation also changes over the
-- same domain it\'s possible to tween GUIs.
--------------------------------------------------------------------------------
-- | Transforms a GUI.
--transformGUI :: (Monad m, Monoid t, Composite a m r t)
--             => Var m i t
--             -- ^ The stream of a changing transformation.
--             -> GUI m i a b
--             -- ^ The GUI to transform.
--             -> GUI m i a b
transformGUI :: (Monad m, Monoid t)
             => Var m i t -> GUI m i [(t, d)] b -> GUI m i [(t, d)] b
transformGUI vt g = GUI $ Var $ \i -> do
    (UX ui e, v) <- runVar (runGUI g) i
    (t, vt') <- runVar vt i
    let ui' = map (first (mappend t)) ui
    return (UX ui' e, runGUI $ transformGUI vt' $ GUI v)
--------------------------------------------------------------------------------
-- $combination
-- Combining two GUIs creates a new GUI.
--------------------------------------------------------------------------------
-- | Combines two GUIs. The resulting GUI will not produce a value until
-- both component GUIs have produced a value. At that moment a merging function
-- is used to combine the two values into the resulting GUI\'s return type.
-- The component GUIs\' graphical representations (the left UX values) are
-- 'mappend'\d together.
combineGUI :: (Monad m, Monoid u)
           => GUI m i u a
           -- ^ The first GUI.
           -> GUI m i u b
           -- ^ The second GUI.
           -> (a -> b -> c)
           -- ^ The merging function.
           -> GUI m i u c
combineGUI (GUI va) (GUI vb) f = GUI $ Var $ \i -> do
        (UX a ea, va') <- runVar va i
        (UX b eb, vb') <- runVar vb i
        return (UX (a <> b) (f <$> ea <*> eb),
                runGUI $ combineGUI (GUI va') (GUI vb') f)