-- | A hook for XMonad window manager to send updates to the
-- corresponding Tianbar widget.
--
-- You must include tianbar:scripts/xmonad.js in Tianbar configuration to
-- receive the updates.
--
-- A "Renderer" can be used to fully customize the output. A renderer is a
-- function receiving all the status information and returning HTML
-- which will be displayed in the corresponding element of the status bar.
--
-- For convenience, a renderer returning 'Markup' can be used as well.
module System.Tianbar.XMonadLog ( dbusLog
                                , dbusLogWithMarkup
                                , dbusLogWithRenderer
                                , tianbarMarkup
                                , WindowSpaceInfo(..)
                                , Renderer
                                , MarkupRenderer
                                ) where

import Data.Maybe

import DBus
import DBus.Client

import Text.Blaze
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Text.Blaze.Renderer.String (renderMarkup)

import XMonad hiding (title, workspaces)
import XMonad.Hooks.UrgencyHook
import qualified XMonad.StackSet as S
import XMonad.Util.NamedWindows
import XMonad.Util.WorkspaceCompare

sig :: Signal
sig = signal (fromJust $ parseObjectPath "/org/xmonad/Log")
             (fromJust $ parseInterfaceName "org.xmonad.Log")
             (fromJust $ parseMemberName "Update")

-- | Workspace information.
data WindowSpaceInfo = WindowSpaceInfo { wsTag     :: String
                                         -- ^ workspace tag
                                       , wsCurrent :: Bool
                                         -- ^ whether the workspace is current
                                       , wsHidden  :: Bool
                                         -- ^ whether the workspace is hidden
                                       , wsUrgent  :: Bool
                                         -- ^ whether the workspace has any
                                         -- urgent windows
                                       , wsEmpty   :: Bool
                                         -- ^ whether the workspace is empty
                                         -- (has no windows)
                                       }

-- | A function to format the status information.
type Renderer a = String            -- ^ layout description
               -> String            -- ^ active window title
               -> [WindowSpaceInfo] -- ^ workspaces
               -> [Window]          -- ^ urgent windows
               -> WindowSet         -- ^ all windows
               -> a

type MarkupRenderer = Renderer Markup

-- | Tianbar logger with a default renderer.
dbusLog :: Client -> X ()
dbusLog client = dbusLogWithMarkup client tianbarMarkup

-- | Tianbar logger with a renderer emitting a string.
dbusLogWithRenderer :: Client -> Renderer String -> X ()
dbusLogWithRenderer client renderer = do
    winset <- gets windowset
    urgents <- readUrgents
    let ld = description . S.layout . S.workspace . S.current $ winset
    wt <- maybe (return "") (fmap show . getName) . S.peek $ winset

    sort_ <- mkWsSort getWsCompare
    let ws = sort_ $ map S.workspace (S.current winset : S.visible winset)
                     ++ S.hidden winset
    let visibles = map (S.tag . S.workspace) (S.visible winset)
    let wsinfo w = WindowSpaceInfo tag_ current_ hidden_ urgent_ empty_
                   where tag_ = S.tag w
                         current_ = tag_ == S.currentTag winset
                         hidden_  = tag_ `notElem` visibles
                         urgent_  = any (\x -> maybe False (== tag_) (S.findTag x winset)) urgents
                         empty_   = isNothing (S.stack w)

    let html = renderer ld wt (map wsinfo ws) urgents winset
    liftIO $ emit client sig { signalBody = [ toVariant html ] }

-- | Tianbar logger with a Blaze renderer.
dbusLogWithMarkup :: Client -> MarkupRenderer -> X ()
dbusLogWithMarkup client renderer = dbusLogWithRenderer client renderer'
    where renderer' ld wt wksp urgent winset =
              renderMarkup $ renderer ld wt wksp urgent winset

-- | Default Tianbar renderer.
tianbarMarkup :: MarkupRenderer
tianbarMarkup layout title workspaces _ _ = do
    H.span ! A.class_ (toValue "workspaces") $
        mapM_ wsHtml workspaces
    H.span ! A.class_ (toValue "layout") $ toMarkup layout
    H.span ! A.class_ (toValue "title") $ toMarkup title
    where
        wsHtml w = H.span ! A.class_ (toValue $ unwords classes) $
            toMarkup $ wsTag w
            where classes =
                    ["workspace"] ++
                    ["current" | wsCurrent w] ++
                    ["hidden"  | wsHidden w] ++
                    ["urgent"  | wsUrgent w] ++
                    ["empty"   | wsEmpty w]