----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.Minimize
-- Copyright   :  (c) Bogdan Sinitsyn (2016)
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Bogdan Sinitsyn <bogdan.sinitsyn@gmail.com>
-- Stability   :  unstable
-- Portability :  not portable
--
-- Adds actions for minimizing and maximizing windows
--
-- This module should be used with "XMonad.Layout.Minimize". Add 'minimize' to your
-- layout modifiers as described in "XMonad.Layout.Minimized" and use actions from
-- this module
--
-- Possible keybindings:
--
-- >        , ((modm,               xK_m     ), withFocused minimizeWindow)
-- >        , ((modm .|. shiftMask, xK_m     ), withLastMinimized maximizeWindowAndFocus)
--
-----------------------------------------------------------------------------

module XMonad.Actions.Minimize
  ( -- * Usage
    -- $usage
    minimizeWindow
  , maximizeWindow
  , maximizeWindowAndFocus
  , withLastMinimized
  , withLastMinimized'
  , withFirstMinimized
  , withFirstMinimized'
  , withMinimized
  ) where

import XMonad
import qualified XMonad.StackSet as W

import qualified XMonad.Layout.BoringWindows as BW
import qualified XMonad.Util.ExtensibleState as XS
import XMonad.Util.Minimize
import XMonad.Util.WindowProperties (getProp32)

import Foreign.C.Types (CLong)
import Control.Applicative((<$>))
import Control.Monad (join)
import Data.Maybe (fromMaybe, listToMaybe)
import qualified Data.List as L
import qualified Data.Map as M

-- $usage
-- Import this module with "XMonad.Layout.Minimize" and "XMonad.Layout.BoringWindows":
-- > import XMonad.Actions.Minimize
-- > import XMonad.Layout.Minimize
-- > import qualified XMonad.Layout.BoringWindows as BW
--
-- Then apply 'minimize' and 'boringWindows' to your layout hook and use some
-- actions from this module:
-- > main = xmonad def { layoutHook = minimize . BW.boringWindows $ whatever }
-- Example keybindings:
-- >        , ((modm,               xK_m     ), withFocused minimizeWindow      )
-- >        , ((modm .|. shiftMask, xK_m     ), withLastMinimized maximizeWindow)

setMinimizedState :: Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState win st f = do
    setWMState win st
    withDisplay $ \dpy -> do
        wm_state <- getAtom "_NET_WM_STATE"
        hidden <- fromIntegral <$> getAtom "_NET_WM_STATE_HIDDEN"
        wstate <- fromMaybe [] <$> getProp32 wm_state win
        io $ changeProperty32 dpy win wm_state aTOM propModeReplace (f hidden wstate)

setMinimized :: Window -> X ()
setMinimized win = setMinimizedState win iconicState (:)

setNotMinimized :: Window -> X ()
setNotMinimized win = setMinimizedState win normalState L.delete

-- It does not just set minimizedStack to newWindows because it should save
-- order in which elements were added (newer first)
modified :: (RectMap -> RectMap) -> X Bool
modified f = XS.modified $
    \Minimized { rectMap = oldRectMap, minimizedStack = oldStack } ->
      let newRectMap = f oldRectMap
          newWindows = M.keys newRectMap
       in Minimized { rectMap = newRectMap
                    , minimizedStack = (newWindows L.\\ oldStack)
                                       ++
                                       (oldStack `L.intersect` newWindows)
                    }


-- | Minimize a window
minimizeWindow :: Window -> X ()
minimizeWindow w = withWindowSet $ \ws ->
  whenX (modified $ M.insert w (M.lookup w $ W.floating ws)) $ do
    setMinimized w
    windows $ W.sink w
    BW.focusDown


-- | Maximize window and apply a function to maximized window and 'WindowSet'
maximizeWindowAndChangeWSet :: (Window -> WindowSet -> WindowSet) -> Window -> X ()
maximizeWindowAndChangeWSet f w = do
  mrect <- XS.gets (join . M.lookup w . rectMap)
  whenX (modified $ M.delete w) $ do
    setNotMinimized w
    broadcastMessage BW.UpdateBoring
    windows $ f w . maybe id (W.float w) mrect

-- | Just maximize a window without focusing
maximizeWindow :: Window -> X ()
maximizeWindow = maximizeWindowAndChangeWSet $ const id

-- | Maximize a window and then focus it
maximizeWindowAndFocus :: Window -> X ()
maximizeWindowAndFocus = maximizeWindowAndChangeWSet W.focusWindow

-- | Perform an action with first minimized window on current workspace
--   or do nothing if there is no minimized windows on current workspace
withFirstMinimized :: (Window -> X ()) -> X ()
withFirstMinimized action = withFirstMinimized' (flip whenJust action)

-- | Like withFirstMinimized but the provided action is always invoked with a
--   'Maybe Window', that will be nothing if there is no first minimized window.
withFirstMinimized' :: (Maybe Window -> X ()) -> X ()
withFirstMinimized' action = withMinimized (action . listToMaybe . reverse)

-- | Perform an action with last minimized window on current workspace
--   or do nothing if there is no minimized windows on current workspace
withLastMinimized :: (Window -> X ()) -> X ()
withLastMinimized action = withLastMinimized' (flip whenJust action)

-- | Like withLastMinimized but the provided action is always invoked with a
--   'Maybe Window', that will be nothing if there is no last minimized window.
withLastMinimized' :: (Maybe Window -> X ()) -> X ()
withLastMinimized' action = withMinimized (action . listToMaybe)

withMinimized :: ([Window] -> X a) -> X a
withMinimized action = do
  minimized <- XS.gets minimizedStack
  currentStack <- withWindowSet $ return . W.index
  action $ minimized `L.intersect` currentStack