{-----------------------------------------------------------------------------
    reactive-banana-wx
------------------------------------------------------------------------------}
{-# LANGUAGE ExistentialQuantification #-}

module Reactive.Banana.WX (
    -- * Synopsis
    -- | Utility functions for interfacing with wxHaskell.
    -- Note: Useful, but I haven't done any serious design work on these.
    
    -- * General
    event1, event0, behavior,
    Prop'(..), sink,
    module Reactive.Banana.Frameworks,
    
    -- * Specific widgets
    eventText, behaviorText, eventSelection,

    -- * Mouse event helpers
    mouseMotion, mouseEnter, mouseLeave,
    leftDown, leftUp, leftDClick, leftDrag,
    rightDown, rightUp, rightDClick, rightDrag,
    middleDown, middleUp, middleDClick, middleDrag,
    mouseWheel, mouseWheelDown, mouseWheelUp,

    -- * Utilities
    event1ToAddHandler, event0ToEvent1,
    ) where

import Reactive.Banana
import Reactive.Banana.Frameworks

import qualified Graphics.UI.WX     as WX
import           Graphics.UI.WX               hiding (Event, Attr)
import qualified Graphics.UI.WXCore as WXCore
-- import Graphics.UI.WX (on, Prop(..))

{-----------------------------------------------------------------------------
    General
------------------------------------------------------------------------------}
-- | Event with exactly one parameter.
event1 :: w -> WX.Event w (a -> IO ()) -> MomentIO (Event a)
event1 widget e = do
    addHandler <- liftIO $ event1ToAddHandler widget e
    fromAddHandler addHandler

    -- NOTE: Some events don't work, for instance   leftKey  and  rightKey
    -- "user error (WX.Events: the key event is write-only.)"
    -- That's because they are actually just derived from the  key  event
    -- Not sure what to do with this.

-- | Event without parameters.
event0 :: w -> WX.Event w (IO ()) -> MomentIO (Event ())
event0 widget = event1 widget . event0ToEvent1

-- | Behavior from an attribute.
-- Uses 'fromPoll', so may behave as you expect.
behavior :: w -> WX.Attr w a -> MomentIO (Behavior a)
behavior widget attr = fromPoll $ get widget attr

-- | Variant of wx properties that accept a 'Behavior'.
data Prop' w = forall a. (WX.Attr w a) :== Behavior a

infixr 0 :==

-- | "Animate" a property with a behavior
sink :: w -> [Prop' w] -> MomentIO ()
sink widget = mapM_ sink1
    where
    sink1 (attr :== b) = do
        x <- valueBLater b
        liftIOLater $ set widget [attr := x]
        e <- changes b
        reactimate' $ (fmap $ \x -> set widget [attr := x]) <$> e

-- | Use a time-varying paint function for a widget,
-- but also make sure that the widget is redrawn whenever said function changes.
sinkPaint :: Paint w => w -> Behavior (DC () -> Rect -> IO ()) -> MomentIO ()
sinkPaint w b = do
    sink w [on paint :== b]
    e <- changes b
    reactimate $ repaint w <$ e

{-----------------------------------------------------------------------------
    Specific widgets
------------------------------------------------------------------------------}
-- | Event that occurs when the /user/ changed
-- the text in text edit widget.
eventText :: TextCtrl w -> MomentIO (Event String)
eventText w = do
    addHandler <- liftIO $ event1ToAddHandler w (event0ToEvent1 onText)
    fromAddHandler
        $ filterIO (const $ WXCore.textCtrlIsModified w)
        $ mapIO (const $ get w text) addHandler

onText :: WX.Event (WXCore.Control a) (IO ())
onText = WX.newEvent "onText" WXCore.controlGetOnText WXCore.controlOnText

-- observe "key up" events (many thanks to Abu Alam)
-- this should probably be in the wxHaskell library
-- keyboardUp  :: WX.Event (Window a) (EventKey -> IO ())
-- keyboardUp  = WX.newEvent "keyboardUp" WXCore.windowGetOnKeyUp WXCore.windowOnKeyUp

-- | Behavior corresponding to user input the text field.
behaviorText :: TextCtrl w -> String -> MomentIO (Behavior String)
behaviorText w s = stepper s =<< eventText w

-- | Event that occurs when the /user/ changed
-- the selection marker in a list box widget.
eventSelection :: SingleListBox b -> MomentIO (Event Int)
eventSelection w = do
    liftIO $ fixSelectionEvent w
    addHandler <- liftIO $ event1ToAddHandler w (event0ToEvent1 select)
    fromAddHandler $ mapIO (const $ get w selection) addHandler

-- Fix @select@ event not being fired when items are *un*selected.
fixSelectionEvent :: (Selecting w, Reactive w, Selection w) => w -> IO ()
fixSelectionEvent listbox =
    set listbox [ on unclick := handler ]
    where
    handler _ = do
        propagateEvent
        s <- get listbox selection
        when (s == -1) $ get listbox (on select) >>= id

{-----------------------------------------------------------------------------
    Mouse event helpers
------------------------------------------------------------------------------}

-- | Return 'Just' in the case that the mouse moves.
mouseMotion :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
mouseMotion (MouseMotion point mod) = Just (point, mod)
mouseMotion _ = Nothing

-- | Return 'Just' in the case that the mouse enters the boundary.
mouseEnter :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
mouseEnter (MouseEnter point mod) = Just (point, mod)
mouseEnter _ = Nothing

-- | Return 'Just' in the case that the mouse leaves the boundary.
mouseLeave :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
mouseLeave (MouseLeave point mod) = Just (point, mod)
mouseLeave _ = Nothing

-- | Return 'Just' in the case that the left mouse button is pressed.
leftDown :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
leftDown (MouseLeftDown point mod) = Just (point, mod)
leftDown _ = Nothing

-- | Return 'Just' in the case that the left mouse button is released.
leftUp :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
leftUp (MouseLeftUp point mod) = Just (point, mod)
leftUp _ = Nothing

-- | Return 'Just' in the case that the left mouse button is double-clicked.
leftDClick :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
leftDClick (MouseLeftDClick point mod) = Just (point, mod)
leftDClick _ = Nothing

-- | Return 'Just' in the case that the mouse is dragged around with the left button
-- pressed.
leftDrag :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
leftDrag (MouseLeftDrag point mod) = Just (point, mod)
leftDrag _ = Nothing

-- | Return 'Just' in the case that the right mouse button is pressed.
rightDown :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
rightDown (MouseRightDown point mod) = Just (point, mod)
rightDown _ = Nothing

-- | Return 'Just' in the case that the right mouse button is released.
rightUp :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
rightUp (MouseRightUp point mod) = Just (point, mod)
rightUp _ = Nothing

-- | Return 'Just' in the case that the right mouse button is double-clicked.
rightDClick :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
rightDClick (MouseRightDClick point mod) = Just (point, mod)
rightDClick _ = Nothing

-- | Return 'Just' in the case that the mouse is dragged around with the right button
-- pressed.
rightDrag :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
rightDrag (MouseRightDrag point mod) = Just (point, mod)
rightDrag _ = Nothing

-- | Return 'Just' in the case that the middle mouse button is pressed.
middleDown :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
middleDown (MouseMiddleDown point mod) = Just (point, mod)
middleDown _ = Nothing

-- | Return 'Just' in the case that the middle mouse button is released.
middleUp :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
middleUp (MouseMiddleUp point mod) = Just (point, mod)
middleUp _ = Nothing

-- | Return 'Just' in the case that the middle mouse button is double-clicked.
middleDClick :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
middleDClick (MouseMiddleDClick point mod) = Just (point, mod)
middleDClick _ = Nothing

-- | Return 'Just' in the case that the mouse is dragged around with the middle button
-- pressed.
middleDrag :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
middleDrag (MouseMiddleDrag point mod) = Just (point, mod)
middleDrag _ = Nothing

-- | Return 'Just' in the case that the mouse wheel is scrolled.
mouseWheel :: WX.EventMouse -> Maybe (Bool, WX.Point, WX.Modifiers)
mouseWheel (MouseWheel down point mod) = Just (down, point, mod)
mouseWheel _ = Nothing

-- | Return 'Just' in the case that the mouse wheel is scrolled downward.
mouseWheelDown :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
mouseWheelDown (MouseWheel True point mod) = Just (point, mod)
mouseWheelDown _ = Nothing

-- | Return 'Just' in the case that the mouse wheel is scrolled upward.
mouseWheelUp :: WX.EventMouse -> Maybe (WX.Point, WX.Modifiers)
mouseWheelUp (MouseWheel False point mod) = Just (point, mod)
mouseWheelUp _ = Nothing

{-----------------------------------------------------------------------------
    Utilities
------------------------------------------------------------------------------}
-- | Obtain an 'AddHandler' from a 'WX.Event'.
event1ToAddHandler :: w -> WX.Event w (a -> IO ()) -> IO (AddHandler a)
event1ToAddHandler widget e = do
    (addHandler, runHandlers) <- newAddHandler
    set widget [on e :~ \h x -> h x >> runHandlers x]
    return addHandler

-- | Obtain an 'AddHandler' from a 'WX.Event'.
event0ToEvent1 :: WX.Event w (IO ()) -> WX.Event w (() -> IO ())
event0ToEvent1 = mapEvent const (\_ e -> e ())