------------------------------------------------------------------------------
-- |
-- Module      :  Utils
-- Copyright   :  (c) Mads N Noe 2010
-- Maintainer  :  mail (@) madsnoe.dk
-- License     :  as-is
--
-- Utility functions for XMonad.
--
------------------------------------------------------------------------------

module Utils where

-- Haskell modules
import Control.Concurrent.MVar
import Control.Monad (unless, when, liftM)
import Control.Monad.Trans (lift)
import Data.List
import Data.Monoid (Endo(Endo))
import System.IO.Error (isDoesNotExistError)
import System.IO.Unsafe (unsafePerformIO)
import System.Posix.Unistd(getSystemID, nodeName)
import qualified Data.Map as M

-- XMonad modules
import XMonad
import XMonad.Actions.CycleWS
import XMonad.Actions.Warp (warpToWindow)
import XMonad.Actions.WindowGo
import XMonad.Hooks.DynamicHooks (oneShotHook)
import XMonad.Hooks.FloatNext
import XMonad.Layout.IndependentScreens
import qualified XMonad.StackSet as W

-- Other moduls
import Graphics.X11.Xinerama
import Graphics.X11.Xlib.Extras


-- GENERAL

-- | Perform k x if x return a 'Just' value.
(?+:: (Monad m) => m (Maybe a) -> (a -> m ()) -> m ()
?+ k = x >>= maybe (return ()) k
infixl 1 ?+

-- | Helper function for use with monads.
if_ :: t -> t -> Bool -> t
if_ t f c = if c 
                  then t
                  else f

-- | Change type to "m ()"
do_ :: (Monad m) => m a -> m ()
do_ x = x >> return ()

quote :: String -> String
quote x = "'" ++ x ++ "'"


-- WINDOW ACTIONS

-- | Swap the focused window with the last window in the stack.
swapBottom :: W.StackSet i l a s sd -> W.StackSet i l a s sd
swapBottom = W.modify' $ \-> case c of
    W.Stack _ _ [] -> c    -- already bottom.
    W.Stack t ls rs -> W.Stack t (xs ++ x : ls) [] where (x:xs) = reverse rs

-- | Swap the focused window with the following window, or if the window is
--   floating, lower it to the bottom.
swapOrLower :: X ()
swapOrLower = withFocused $ \->
    runQuery isFloat w >>= if_ (windows swapBottom) (windows W.swapDown)

-- | Swap the focused window with the preceding window, or if the window is
--   floating, raise it to the top.
swapOrRaise :: X ()
swapOrRaise = withFocused $ \->
    runQuery isFloat w >>= if_ (windows W.swapMaster) (windows W.swapUp)

-- spawnOnThisWS :: GHC.IOBase.IORef XMonad.Hooks.DynamicHooks.DynamicHooks-> Query Bool-> String-> X ()
spawnOnThisWS dhr q cmd = withWindowSet $ \ws -> do
    oneShotHook dhr q $ doF $ W.shift $ W.currentTag ws
    spawn cmd

-- | Warp the mouse pointer to the focused window only if the workspace has
--   no floating windows to steal the focus.
warpToWindow' = withWindowSet $ \ws -> do
    let floats  = M.keys $ W.floating ws
        visible = W.integrate' $ W.stack $ W.workspace $ W.current ws
        vf      = floats `intersect` visible
    when (null vf) $ warpToWindow (1/2) (1/2)


-- QUERIES ETC

-- | Is the focused window the \"master window\" of the current workspace?
isMaster :: Query Bool
isMaster = ask >>= (\-> liftX $ withWindowSet $ \ws -> return $ Just w == master ws)
  where
    master :: WindowSet -> Maybe Window
    master ws = 
        case W.integrate' $ W.stack $ W.workspace $ W.current ws of
             [] -> Nothing
             (x:xs) -> Just x

-- | Is the focused window a floating window?
isFloat :: Query Bool
isFloat = ask >>= (\-> liftX $ withWindowSet $ \ws -> return $ M.member w $ W.floating ws)

-- | Helper to read a property
-- getProp :: Atom -> Window -> X (Maybe [CLong])
getProp a w = withDisplay $ \dpy -> io $ getWindowProperty32 dpy a w

-- | Check if window is DIALOG window
checkDialog :: Query Bool
checkDialog = ask >>= \-> liftX $ do
                a <- getAtom "_NET_WM_WINDOW_TYPE"
                dialog <- getAtom "_NET_WM_WINDOW_TYPE_DIALOG"
                mbr <- getProp a w
                case mbr of
                  Just [r] -> return $ elem (fromIntegral r) [dialog]
                  _ -> return False

-- | Determine the number of physical screens.
countScreens :: (MonadIO m, Integral i) => m i
countScreens = liftM genericLength . liftIO $ openDisplay "" >>= getScreenInfo


-- HOST

-- | For use in cross host configutions.
data Host = Laptop | Netbook deriving Eq

-- | Determine the host.
getHost = do 
    host <- getSystemID
    case nodeName host of
         "mntnoe-laptop"  -> return Laptop
         "mntnoe-netbook" -> return Netbook
         _                -> return Laptop


-- MISC

-- | Return a string that launches xterm with the given 'title', 'appName' and
--   command to execute.
xterm :: String -> String -> String
xterm a e = concat ["xterm -wf -title '", e,  "' -name '", a, "' -e '", e, "'"]