{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, MultiParamTypeClasses, TypeSynonymInstances, PatternGuards #-}

----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.Hidden
-- Copyright   :  (c) Peter Jones 2015
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  pjones@devalot.com
-- Stability   :  unstable
-- Portability :  not portable
--
-- Similar to "XMonad.Layout.Minimize" but completely removes windows
-- from the window set so "XMonad.Layout.BoringWindows" isn't
-- necessary.  Perfect companion to
-- "XMonad.Layout.BinarySpacePartition" since it can be used to move
-- windows to another part of the BSP tree.
--
-----------------------------------------------------------------------------
module XMonad.Layout.Hidden
       ( -- * Usage
         -- $usage
         HiddenMsg (..)
       , hiddenWindows
       , hideWindow
       , popOldestHiddenWindow
       , popNewestHiddenWindow
       ) where

--------------------------------------------------------------------------------
import XMonad
import XMonad.Layout.LayoutModifier
import qualified XMonad.StackSet as W

--------------------------------------------------------------------------------
-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Layout.Hidden
--
-- Then edit your @layoutHook@ by adding the @HiddenWindows@ layout modifier:
--
-- > myLayout = hiddenWindows (Tall 1 (3/100) (1/2)) ||| Full ||| etc..
-- > main = xmonad def { layoutHook = myLayout }
--
-- For more detailed instructions on editing the layoutHook see:
--
-- "XMonad.Doc.Extending#Editing_the_layout_hook"
--
-- In the key bindings, do something like:
--
-- >        , ((modMask, xK_backslash), withFocused hideWindow)
-- >        , ((modMask .|. shiftMask, xK_backslash), popOldestHiddenWindow)
-- >        ...
--
-- For detailed instruction on editing the key bindings see:
--
-- "XMonad.Doc.Extending#Editing_key_bindings".

--------------------------------------------------------------------------------
data HiddenWindows a = HiddenWindows [Window] deriving (Show, Read)

--------------------------------------------------------------------------------
-- | Messages for the @HiddenWindows@ layout modifier.
data HiddenMsg = HideWindow Window       -- ^ Hide a window.
               | PopNewestHiddenWindow   -- ^ Restore window (FILO).
               | PopOldestHiddenWindow   -- ^ Restore window (FIFO).
               deriving (Typeable, Eq)

instance Message HiddenMsg

--------------------------------------------------------------------------------
instance LayoutModifier HiddenWindows Window where
  handleMess h@(HiddenWindows hidden) mess
    | Just (HideWindow win)        <- fromMessage mess = hideWindowMsg h win
    | Just (PopNewestHiddenWindow) <- fromMessage mess = popNewestMsg h
    | Just (PopOldestHiddenWindow) <- fromMessage mess = popOldestMsg h
    | Just ReleaseResources        <- fromMessage mess = doUnhook
    | otherwise                                        = return Nothing
    where doUnhook = do mapM_ restoreWindow hidden
                        return Nothing

  modifierDescription _ = "Hidden"

--------------------------------------------------------------------------------
-- | Apply the @HiddenWindows@ layout modifier.
hiddenWindows :: LayoutClass l Window => l Window -> ModifiedLayout HiddenWindows l Window
hiddenWindows = ModifiedLayout $ HiddenWindows []

--------------------------------------------------------------------------------
-- | Remove the given window from the current layout.  It is placed in
-- list of hidden windows so it can be restored later.
hideWindow :: Window -> X ()
hideWindow = sendMessage . HideWindow

--------------------------------------------------------------------------------
-- | Restore a previously hidden window.  Using this function will
-- treat the list of hidden windows as a FIFO queue.  That is, the
-- first window hidden will be restored.
popOldestHiddenWindow :: X ()
popOldestHiddenWindow = sendMessage PopOldestHiddenWindow

--------------------------------------------------------------------------------
-- | Restore a previously hidden window.  Using this function will
-- treat the list of hidden windows as a FILO queue.  That is, the
-- most recently hidden window will be restored.
popNewestHiddenWindow :: X ()
popNewestHiddenWindow = sendMessage PopNewestHiddenWindow

--------------------------------------------------------------------------------
hideWindowMsg :: HiddenWindows a -> Window -> X (Maybe (HiddenWindows a))
hideWindowMsg (HiddenWindows hidden) win = do
  modify (\s -> s { windowset = W.delete' win $ windowset s })
  return . Just . HiddenWindows $ hidden ++ [win]

--------------------------------------------------------------------------------
popNewestMsg :: HiddenWindows a -> X (Maybe (HiddenWindows a))
popNewestMsg (HiddenWindows [])     = return Nothing
popNewestMsg (HiddenWindows hidden) = do
  let (win, rest) = (last hidden, init hidden)
  restoreWindow win
  return . Just . HiddenWindows $ rest

--------------------------------------------------------------------------------
popOldestMsg :: HiddenWindows a -> X (Maybe (HiddenWindows a))
popOldestMsg (HiddenWindows [])         = return Nothing
popOldestMsg (HiddenWindows (win:rest)) = do
  restoreWindow win
  return . Just . HiddenWindows $ rest

--------------------------------------------------------------------------------
restoreWindow :: Window -> X ()
restoreWindow win =
  modify (\s -> s { windowset = W.insertUp win $ windowset s })