module XMonad.Hooks.ManageHelpers (
    Side(..),
    composeOne,
    (-?>), (/=?), (<==?), (</=?), (-->>), (-?>>),
    currentWs,
    isInProperty,
    isKDETrayWindow,
    isFullscreen,
    isDialog,
    pid,
    transientTo,
    maybeToDefinite,
    MaybeManageHook,
    transience,
    transience',
    doRectFloat,
    doFullFloat,
    doCenterFloat,
    doSideFloat,
    doFloatAt,
    doFloatDep,
    doHideIgnore,
    Match,
) where
import XMonad
import qualified XMonad.StackSet as W
import XMonad.Util.WindowProperties (getProp32s)
import Data.Maybe
import Data.Monoid
import System.Posix (ProcessID)
data Side = SC | NC | CE | CW | SE | SW | NE | NW | C
    deriving (Read, Show, Eq)
type MaybeManageHook = Query (Maybe (Endo WindowSet))
data Match a = Match Bool a
composeOne :: [MaybeManageHook] -> ManageHook
composeOne = foldr try idHook
    where
    try q z = do
        x <- q
        case x of
            Just h -> return h
            Nothing -> z
infixr 0 -?>, -->>, -?>>
(/=?) :: Eq a => Query a -> a -> Query Bool
q /=? x = fmap (/= x) q
(<==?) :: Eq a => Query a -> a -> Query (Match a)
q <==? x = fmap (`eq` x) q
    where
    eq q' x' = Match (q' == x') q'
(</=?) :: Eq a => Query a -> a -> Query (Match a)
q </=? x = fmap (`neq` x) q
    where
    neq q' x' = Match (q' /= x') q'
(-?>) :: Query Bool -> ManageHook -> MaybeManageHook
p -?> f = do
    x <- p
    if x then fmap Just f else return Nothing
(-->>) :: Query (Match a) -> (a -> ManageHook) -> ManageHook
p -->> f = do
    Match b m <- p
    if b then (f m) else mempty
(-?>>) :: Query (Match a) -> (a -> ManageHook) -> MaybeManageHook
p -?>> f = do
    Match b m <- p
    if b then fmap  Just (f m) else return Nothing
currentWs :: Query WorkspaceId
currentWs = liftX (withWindowSet $ return . W.currentTag)
isKDETrayWindow :: Query Bool
isKDETrayWindow = ask >>= \w -> liftX $ do
    r <- getProp32s "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR" w
    return $ case r of
        Just [_] -> True
        _ -> False
isInProperty :: String -> String -> Query Bool
isInProperty p v = ask >>= \w -> liftX $ do
    va <- getAtom v
    r <- getProp32s p w
    return $ case r of
        Just xs -> fromIntegral va `elem` xs
        _ -> False
isFullscreen :: Query Bool
isFullscreen = isInProperty "_NET_WM_STATE" "_NET_WM_STATE_FULLSCREEN"
isDialog :: Query Bool
isDialog = isInProperty "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_DIALOG"
pid :: Query (Maybe ProcessID)
pid = ask >>= \w -> liftX $ do
    p <- getProp32s "_NET_WM_PID" w
    return $ case p of
        Just [x] -> Just (fromIntegral x)
        _        -> Nothing
transientTo :: Query (Maybe Window)
transientTo = do
    w <- ask
    d <- (liftX . asks) display
    liftIO $ getTransientForHint d w
transience :: MaybeManageHook
transience = transientTo </=? Nothing -?>> move
    where
    move mw = maybe idHook (doF . move') mw
    move' w s = maybe s (`W.shift` s) (W.findTag w s)
transience' :: ManageHook
transience' = maybeToDefinite transience
maybeToDefinite :: MaybeManageHook -> ManageHook
maybeToDefinite = fmap (fromMaybe mempty)
doRectFloat :: W.RationalRect  
            -> ManageHook
doRectFloat r = ask >>= \w -> doF (W.float w r)
doFullFloat :: ManageHook
doFullFloat = doRectFloat $ W.RationalRect 0 0 1 1
doFloatDep :: (W.RationalRect -> W.RationalRect) -> ManageHook
doFloatDep move = ask >>= \w -> doF . W.float w . move . snd =<< liftX (floatLocation w)
doFloatAt :: Rational -> Rational -> ManageHook
doFloatAt x y = doFloatDep move
  where
    move (W.RationalRect _ _ w h) = W.RationalRect x y w h
doSideFloat :: Side -> ManageHook
doSideFloat side = doFloatDep move
  where
    move (W.RationalRect _ _ w h) = W.RationalRect cx cy w h
      where cx =      if side `elem` [SC,C ,NC] then (1w)/2
                 else if side `elem` [SW,CW,NW] then 0
                 else    1w
            cy =      if side `elem` [CE,C ,CW] then (1h)/2
                 else if side `elem` [NE,NC,NW] then 0
                 else    1h
doCenterFloat :: ManageHook
doCenterFloat = doSideFloat C
doHideIgnore :: ManageHook
doHideIgnore = ask >>= \w -> liftX (hide w) >> doF (W.delete w)