{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
-- | Designed to be imported as @qualified@.
module Hbro.Prompt(
    PromptBar(..),
    onChanged,
    onValidated,
    entry,
    description,
    box,
    PromptReader(..),
    init,
    open,
    hide,
    clean,
    read,
    incrementalRead,
    iread,
    readURI,
    getEntryValue)
where

-- {{{ Imports
import Hbro.Error
-- import Hbro.Gui
import Hbro.Network
import Hbro.Notification
import Hbro.Util

import Control.Conditional hiding(when)
import Control.Lens hiding((??))
import Control.Monad hiding(forM_, mapM_)
import Control.Monad.Base
import Control.Monad.Error hiding(forM_, mapM_, when)
import Control.Monad.Writer
import Control.Monad.Trans.Control

-- import Data.Foldable
-- import Data.Functor
import Data.IORef

import Graphics.Rendering.Pango.Enums
import Graphics.UI.Gtk.Abstract.Widget
-- import Graphics.UI.Gtk.Builder
import Graphics.UI.Gtk.Display.Label
import Graphics.UI.Gtk.Entry.Editable
import Graphics.UI.Gtk.Entry.Entry
import Graphics.UI.Gtk.Gdk.EventM
import Graphics.UI.Gtk.Layout.HBox

import Network.URI hiding(parseURIReference)

import Prelude hiding(init, mapM_, read)

import System.Glib.Signals
-- }}}

-- {{{ Types
data PromptBar m = PromptBar {
    _box           :: HBox,
    _description   :: Label,
    _entry         :: Entry,
    _onChanged     :: IORef (String -> m ()),
    _onValidated   :: IORef (String -> m ())}

makeLenses ''PromptBar

class (Monad m, Monad n) => PromptReader n m | m -> n where
    readPrompt :: Simple Lens (PromptBar n) a -> m a
-- }}}

-- Validate/cancel prompt
onEntryValidated :: (MonadBase IO m, MonadBaseControl IO m, NotificationReader m, Error e, Show e, MonadError e m, EntryClass t) => t -> (String -> m ()) -> m (ConnectId t)
onEntryValidated entry' f = liftBaseWith $ \runInIO -> on entry' keyPressEvent $ do
    key <- eventKeyName
    io $ when (key == "Return") $ do
        void . runInIO $ (io (entryGetText entry') >>= f) `catchError` \e -> io (print e) >> notify 5000 (show e)
    return False

-- Incremental behavior
onEntryChanged :: (MonadBaseControl IO m, NotificationReader m, Error e, Show e, MonadError e m, EditableClass t, EntryClass t) => t -> (String -> m ()) -> m (ConnectId t)
onEntryChanged entry' f = liftBaseWith $ \runInIO -> on entry' editableChanged $ do
    void . runInIO $ (io (entryGetText entry') >>= f) `catchError` \e -> io (print e) >> notify 5000 (show e)


init :: (MonadBase IO m, MonadBaseControl IO m, NotificationReader m, Error e, Show e, MonadError e m) => PromptBar m -> m ()
init promptBar = do
    io $ labelSetAttributes l [allItalic, allBold]
    io $ labelSetAttributes l [AttrForeground {paStart = 0, paEnd = -1, paColor = Color 32767 32767 32767}]

    io $ widgetModifyBase entry' StateNormal $ Color 0 0 0
    io $ widgetModifyText entry' StateNormal $ Color 32767 32767 32767

    void . onEntryChanged   entry' $ \v -> io (readIORef onChanged')   >>= \f -> f v
    void . onEntryValidated entry' $ \v -> io (readIORef onValidated') >>= \f -> f v
  where
    l            = _description promptBar
    entry'       = _entry promptBar
    onChanged'   = _onChanged promptBar
    onValidated' = _onValidated promptBar


open :: (Functor m, MonadBase IO m, PromptReader n m, MonadWriter String m) => String -> String -> m ()
open newDescription defaultText = do
    tell "Opening prompt."
    e <- readPrompt entry
    io . (`labelSetText` newDescription) =<< readPrompt description
    io $ entrySetText e defaultText
    io . widgetShow =<< readPrompt box
    io $ widgetGrabFocus e
    io $ editableSetPosition e (-1)

hide :: (MonadBase IO m, PromptReader n m) => m ()
hide = io . widgetHide =<< readPrompt box

-- | Close prompt, clean its content and callbacks
clean :: (MonadBase IO m, PromptReader n m) => m ()
clean = do
     e <- readPrompt entry
     io $ (`widgetRestoreText` StateNormal) e
     io . widgetModifyText e StateNormal $ Color 32767 32767 32767
     hide

     readPrompt onChanged   >>= io . (`writeIORef` return (return ()))
     readPrompt onValidated >>= io . (`writeIORef` return (return ()))
     return ()

-- | Open prompt bar with given description and default value,
-- and register a callback to trigger at validation.
read :: (MonadBaseControl IO m, PromptReader m m, Error e, MonadError e m, MonadWriter String m)
     => String        -- ^ Prompt description
     -> String        -- ^ Initial value
     -> (String -> m ())    -- ^ Function to trigger when validating prompt value
     -> m ()
read = read' False

-- | Same as 'read', but callback is triggered for each change in prompt's entry.
incrementalRead :: (MonadBase IO m, MonadBaseControl IO m, PromptReader m m, Error e, MonadError e m, MonadWriter String m) => String -> String -> (String -> m ())  -> m ()
incrementalRead = read' True

-- | Alias for 'incrementalRead'.
iread :: (MonadBaseControl IO m, PromptReader m m, Error e, MonadError e m, MonadWriter String m) => String -> String -> (String -> m ())  -> m ()
iread = incrementalRead

read' :: (MonadBaseControl IO m, PromptReader m m, Error e, MonadError e m, MonadWriter String m) => Bool -> String -> String -> (String -> m ()) -> m ()
read' incremental description' startValue f = do
    clean
    open description' startValue

    when incremental $ readPrompt onChanged >>= io . (`writeIORef` f)
    readPrompt onValidated >>= io . (`writeIORef` (f >=> const clean))
    return ()


-- | Same as 'read' for URI values
readURI :: (MonadBase IO m, PromptReader m m, MonadError HError m, MonadWriter String m) => String -> String -> (URI -> m ()) -> m ()
readURI description' startValue callback = do
    clean
    open description' startValue
    checkURI startValue

    readPrompt onChanged   >>= io . (`writeIORef` checkURI)
    readPrompt onValidated >>= io . (`writeIORef` (parseURIReference >=> callback >=> const clean))
    return ()
  where
    checkURI v = do
        e <- readPrompt entry
        io $ widgetModifyText e StateNormal color
      where
        color = (isURIReference v) ? green ?? red
        green = Color     0 65535 0
        red   = Color 65535     0 0


getEntryValue :: (MonadBase IO m, PromptReader n m) => m String
getEntryValue = io . entryGetText =<< readPrompt entry