import XMonad.Actions.UpdatePointer (updatePointer)

import XMonad.Layout.IndependentScreens (countScreens)

import Control.Monad.Reader (asks, ask)
import Control.Monad.State (gets)
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad (mapM, liftM2)
import Control.Applicative ((<$>))
import Data.Bits ((.|.))
import Data.List ( intercalate, elemIndex )
import Data.Char (isSpace)
import XMonad.Util.NamedWindows (getName)
import XMonad (xmonad)
import XMonad.Core
    ( config
    , Layout
    , ScreenDetail
    , ScreenId (S)
    , terminal
    , modMask
    , borderWidth
    , normalBorderColor
    , focusedBorderColor
    , workspaces
    , keys
    , mouseBindings
    , layoutHook
    , manageHook
    , handleEventHook
    , XConfig (..)
    , X (..)
    , spawn
    , withWindowSet
    , whenJust
    , runQuery
    , windowset
    , WorkspaceId
    , WindowSpace
    , WindowSet
    , description
    )
import XMonad.Config (def)
import Graphics.X11.Types
    ( Window , ButtonMask
    , KeyMask , KeySym
    , mod1Mask , mod4Mask , shiftMask , controlMask
    , xK_Return , xK_Escape , xK_Insert , xK_Print
    , xK_Right , xK_Left , xK_space
    , xK_plus , xK_minus , xK_comma , xK_period
    , button1 , button2 , button3
    , xK_1 , xK_2 , xK_3 , xK_4 , xK_5 , xK_6 , xK_7 , xK_8 , xK_9 , xK_0
    , xK_b , xK_c , xK_e , xK_f , xK_g , xK_h , xK_j , xK_k , xK_l , xK_m
    , xK_n , xK_p , xK_q , xK_r , xK_s , xK_t , xK_w , xK_y , xK_z
    )

import Graphics.X11.ExtraTypes.XF86
    ( xF86XK_AudioMute
    , xF86XK_AudioLowerVolume , xF86XK_AudioRaiseVolume
    , xF86XK_AudioPlay , xF86XK_AudioStop
    , xF86XK_AudioPrev , xF86XK_AudioNext
    )

import XMonad.Util.Types
    ( Direction2D (U, D, L, R)
    , Direction1D (Next)
    )

import XMonad.Hooks.ManageDocks
    ( docks
    , ToggleStruts (..)
    , avoidStruts
    )

import XMonad.Layout
    ( Full (..)
    , Mirror (..)
    , IncMasterN (..)
    , Resize (Shrink, Expand)
    , (|||)
    , ChangeLayout (NextLayout)
    )

import XMonad.ManageHook
    ( className
    , (=?)
    , resource
    , composeAll
    , (-->)
    , doFloat
    , appName
    , doShift
    , stringProperty
    , doF
    )
import XMonad.Operations
    ( windows
    , sendMessage
    , setLayout
    , withFocused
    , screenWorkspace
    , focus
    , mouseMoveWindow
    , mouseResizeWindow
    )

import XMonad.StackSet
    ( StackSet (..)
    , RationalRect (..)
    , Workspace (..)
    , shift
    , visible
    , screen
    , current
    , swapUp
    , swapDown
    , sink
    , view
    , greedyView
    , hidden
    , tag
    , workspace
    , shiftMaster
    , floating
    , stack
    , allWindows
    )
import qualified XMonad.StackSet as S

import Data.Monoid (appEndo)
import Data.Ratio ((%))
import Text.Printf (printf)
import qualified Data.Map as M

import XMonad.Util.Run
    ( safeSpawn
    , spawnPipe
    , hPutStrLn
    , runProcessWithInput
    )
import XMonad.Util.Loggers (Logger)
import XMonad.Util.Paste (pasteSelection)
import XMonad.Util.NamedScratchpad
    ( NamedScratchpad (NS)
    , customFloating
    , namedScratchpadManageHook
    , namedScratchpadAction
    )

import XMonad.Layout.Spacing
    ( toggleScreenSpacingEnabled
    , toggleWindowSpacingEnabled
    , spacingRaw
    , Border (..)
    )
import XMonad.Layout.Grid (Grid (..))

import XMonad.Layout.Tabbed
    ( tabbed
    , activeColor
    , inactiveColor
    , activeBorderColor
    , inactiveBorderColor
    , urgentColor
    , activeTextColor
    , inactiveTextColor
    , urgentTextColor
    , fontName
    , shrinkText
    )
import XMonad.Layout.NoBorders (noBorders, smartBorders)
import XMonad.Layout.ThreeColumns (ThreeCol (ThreeColMid))
import XMonad.Layout.ResizableTile
    ( ResizableTall (..)
    , MirrorResize (MirrorShrink, MirrorExpand)
    )
import XMonad.Layout.Named (named)
import XMonad.Layout.PerWorkspace (onWorkspace)

import XMonad.Layout.BoringWindows (boringWindows, focusUp, focusDown)
import XMonad.Layout.Minimize (minimize)
import XMonad.Actions.Minimize
    ( minimizeWindow
    , withLastMinimized
    , maximizeWindowAndFocus
    )
import XMonad.Layout.Maximize (maximizeWithPadding, maximizeRestore)

import XMonad.Actions.CopyWindow (copyWindow, copy, kill1)
import XMonad.Actions.FloatKeys (keysResizeWindow, keysMoveWindow)
import XMonad.Actions.Commands (workspaceCommands, screenCommands)

import XMonad.Util.WorkspaceCompare

import XMonad.Actions.CycleWS
    ( toggleWS'
    , findWorkspace
    , WSType (WSIs)
    )

import XMonad.Actions.Navigation2D
    ( withNavigation2DConfig
    , centerNavigation
    , singleWindowRect
    , switchLayer
    , defaultTiledNavigation
    , layoutNavigation
    , unmappedWindowRect
    , windowGo
    , windowSwap
    )

import XMonad.Hooks.DynamicLog
    ( ppCurrent
    , dynamicLogWithPP
    , ppVisible
    , ppOutput
    , ppHidden
    , ppHiddenNoWindows
    , ppUrgent
    , ppTitle
    , ppSep
    , ppExtras
    , ppLayout
    , wrap
    , xmobarColor
    , shorten
    , PP
    )
import XMonad.Hooks.ManageHelpers (isFullscreen, doFullFloat, doRectFloat)
import XMonad.Hooks.EwmhDesktops (ewmh)
import XMonad.Hooks.ServerMode (serverModeEventHookCmd)



main :: IO ()
main = do nscreens <- countScreens
          let myScreens = [0 .. nscreens-1]
          xmprocs <- mapM (spawnPipe . xmobarCmd) myScreens
          let bars = mapM_ dynamicLogWithPP $ zipWith pp xmprocs myScreens
          setupOut <- runProcessWithInput "/home/rilla/bin/get_setup" [] ""
          let setup = read $ filter (not . isSpace) setupOut :: Int
              sb = case setup of
                       0 -> addConfMod screenBinder [] []
                       1 -> addConfMod screenBinder [xK_w, xK_e] [0, 1]
                       2 -> addConfMod screenBinder [xK_w, xK_e] [1, 0]
              mouseFollow = not $ setup == 1
          xmonad $ opts def
              { terminal           = myTerminal
              , modMask            = myModMask
              , borderWidth        = myBorderWidth
              , normalBorderColor  = inactiveBorder
              , focusedBorderColor = myActiveColor
              , workspaces         = myWorkspaces
              , keys               = keyComb myKeys sb
              , mouseBindings      = myMouseBindings
              , layoutHook         = myLayoutHook
              , manageHook         = myManageHook
              , logHook            = bars
              , handleEventHook    = serverModeEventHookCmd
              , focusFollowsMouse  = mouseFollow
              }
    where opts = docks . ewmh . withNavigation2DConfig myNav2DConf

xmobarCmd i = unwords ["xmobar -x" ++ show i, configFile]
    where configFile = "~/.xmonad/xmobar.hs"

myNav2DConf = def { defaultTiledNavigation = centerNavigation
                  , layoutNavigation   = [("Full", centerNavigation)]
                  , unmappedWindowRect = [("Full", singleWindowRect)]
                  }


myCommands :: X [(String, X ())]
myCommands = do wscmds <- workspaceCommands
                wsccmds <- workspaceCopyCommands
                return $ wscmds ++ wsccmds ++ screenCommands ++ otherCommands
    where otherCommands = []

          workspaceCopyCommands = fmap (map f) (asks (workspaces . config))
              where f x = ("copy" ++ show x, windows $ copy x)

myModMask = mod4Mask
myTerminal = "termite"

-- Workspaces
myws1 = "1"
myws2 = "2"
myws3 = "3"
myws4 = "4"
myws5 = "5"
myws6 = "6"
myws7 = "7"
myws8 = "8"
myws9 = "9"

myWorkspaces :: [String]
myWorkspaces = [myws1, myws2, myws3, myws4, myws5, myws6 , myws7, myws8, myws9]

myLayoutHook = fancyThings $ onWorkspace myws9 lws9 allBut9

    where tall  = named "tall" $ ResizableTall 1 delta ratio []
          mtall = named "mtall" $ Mirror tall
          tabs  = named "tabs" $ tabbed shrinkText myDecorationTheme
          cols  = named "3cols" $ ThreeColMid 1 delta (1/3)
          full  = named "full" Full
          grid  = named "grid" Grid
          mono  = named "empty" $ noBorders Full

          allBut9 = spaces spaced ||| tabs ||| full
              where spaced = tall ||| mtall ||| cols
          lws9 = spaces $ grid ||| tall ||| tabs ||| full

          spaces = spacingRaw False b True b True
              where b = Border defSpacing defSpacing defSpacing defSpacing
                    defSpacing = 10

          fancyThings = avoidStruts . smartBorders . maxMin . boringWindows
              where maxMin = maximizeWithPadding 0 . minimize
          ratio = 1/2
          delta = 3/100


myScratchpads = [ NS "terminal"  spawnTerm      findTerm      mngTopScratch
                , NS "mixer"     spawnMixer     findMixer     mngSmallerFloat
                , NS "files"     spawnFiles     findFiles     mngSmallFloat
                , NS "whatsapp"  spawnWhatsapp  findWhatsapp  mngSmallFloat
                , NS "telegram"  spawnTelegram  findTelegram  mngSmallFloat
                , NS "calendar"  spawnCalendar  findCalendar  mngBiggerFloat
                , NS "profanity" spawnProfanity findProfanity mngSmallFloat
                , NS "top"       spawnTop       findTop       mngBigFloat
                , NS "rtop"      spawnRTop      findRTop      mngBigFloat
                ]

    where spawnMixer  = "pavucontrol"
          findMixer   = className =? "Pavucontrol"

          spawnFiles  = "termite --name=files_term -e ranger"
          findFiles   = resource =? "files_term"

          spawnWhatsapp  = "chromium --app=https://web.whatsapp.com/"
          findWhatsapp   = resource =? "web.whatsapp.com"

          spawnTelegram  = "telegram-desktop"
          findTelegram   = resource =? "telegram-desktop"

          spawnCalendar  = "chromium --app=https://calendar.google.com/"
          findCalendar   = resource =? "calendar.google.com"

          spawnProfanity = "termite --name=profanity -e profanity"
          findProfanity  = resource =? "profanity"

          spawnTop = "termite --name=top -e htop"
          findTop  = resource =? "top"

          spawnRTop = "termite --name=rtop -e /home/rilla/bin/remote_top"
          findRTop  = resource =? "rtop"

          spawnTerm  = "termite --name=scratchpad"
          findTerm   = resource =? "scratchpad"

          mngSmallerFloat = centeredFloat 0.6
          mngSmallFloat = centeredFloat 0.7
          mngBigFloat = centeredFloat 0.8
          mngBiggerFloat = centeredFloat 0.9
          centeredFloat s = customFloating $ RationalRect p p s s
              where p = (1-s) / 2

          mngTopScratch = customFloating $ RationalRect l t w h
              where h = 0.3    -- height, 30%
                    w = 1      -- width, 100%
                    t = 0      -- distance from top edge, 0%
                    l = 1 - w  -- distance from left edge, 0%


myManageHook = composeAll
    [ isFullscreen --> doFullFloat
    , className =? "Oblogout"      --> doFullFloat
    , className =? "MPlayer"       --> doFloat
    , className =? "mpv"           --> doFloat
    , className =? "VirtualBox"    --> doFloat
    , className =? "Pinentry"      --> doFloat
    , className =? "qjackctl"      --> doFloat
    , className =? "Xmessage"      --> doFloat
    , className =? "SuperCollider" --> doFloat
    , role      =? "gimp-dock"     --> doFloat
    , role =? "GtkFileChooserDialog" --> doFloat

    -- music
    , appName   =? "mpsyt"   --> doShift musicWs
    , appName   =? "ncmpcpp" --> doShift musicWs
    , appName   =? "vis"     --> doShift musicWs
    , className =? "Spotify" --> doShift musicWs

    -- mutt
    , appName =? "mutt" --> doShift mailWs

    -- dev
    , className =? "Openscad" --> doShift myws3
    , appName   =? "dev_term" --> doShift myws2
    , appName   =? "dev_code" --> copyHook myws2 myws3

    --, manageScratchPad
    , namedScratchpadManageHook myScratchpads
    ]
    where role     = stringProperty "WM_WINDOW_ROLE"
          musicWs  = myws9
          mailWs   = myws8
          copyHook ws1 ws2 = let f w = (`copyWindow` ws2) w . shift ws1
                             in ask >>= doF . f

-- Looks
myBorderWidth = 3

inactiveBorder  = "#7f8289"
myActiveColor   = "#cc342b"
myInactiveColor = "#c0c5ce"
myUrgentColor   = "#a3be8c"
bgNormal        = "#282828"
activeTabColor  = "#3c3836"
fgFocus         = "#f8f9fa"
hiddenColor     = "#4f5b66"

myDecorationTheme = def { activeColor         = myInactiveColor
                        , inactiveColor       = bgNormal
                        , urgentColor         = myUrgentColor
                        , activeBorderColor   = bgNormal
                        , inactiveBorderColor = bgNormal
                        , activeTextColor     = activeTabColor
                        , inactiveTextColor   = myInactiveColor
                        , urgentTextColor     = myUrgentColor
                        , fontName            = "xft:Inconsolata:size=12"
                        }

dmenu :: MonadIO m => String -> m ()
dmenu x = spawn $ unwords cmd
    where cmd = [ x, "-i -b"
                , "-fn", quote font
                , "-nb", quote bgNormal
                , "-nf", quote fgNormal
                , "-sb", quote bgFocus
                , "-sf", quote fgFocus
                ]
          quote x  = "\"" ++ x ++ "\""
          font     = "Inconsolata-10"
          fgNormal = myInactiveColor
          bgFocus  = myActiveColor
          fgFocus  = fgFocus

dmenuRun :: MonadIO m => m ()
dmenuRun = spawn "/home/rilla/bin/dmenu_themed dmenu_run"

passmenu :: MonadIO m => m ()
passmenu = spawn "/home/rilla/bin/dmenu_themed passmenu"

uglyDmenuRunLocal :: MonadIO m => m ()
uglyDmenuRunLocal = dmenu "/home/rilla/bin/ugly_dmenu_run"

uglyDmenuRunRemote :: MonadIO m => m ()
uglyDmenuRunRemote = dmenu "/home/rilla/bin/ugly_remote_dmenu_run"

-- Xmobar


pp bar screen = common { ppOutput = hPutStrLn bar
                       , ppExtras = [ workspaceLog screen
                                    , layoutLog screen
                                    , taskbar screen
                                    ]
                       }
    where common = def { ppCurrent         = const ""
                       , ppVisible         = const ""
                       , ppHidden          = const ""
                       , ppHiddenNoWindows = const ""
                       , ppUrgent          = xmobarColor myUrgentColor ""
                       , ppTitle           = const ""
                       , ppSep             = xmobarColor hiddenColor "" " | "
                       , ppLayout          = const ""
                       }


type Monitor = S.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail

getScreen :: WindowSet -> Int -> Monitor
getScreen ws i = screenById i $ S.screens ws
    where screenById i = head . filter ((== S i) . S.screen)

isScreenFocused :: WindowSet -> Int -> Bool
isScreenFocused x i = focusedScreen == myScreen
    where focusedScreen = screen $ current x
          myScreen = screen $ getScreen x i

logScreen :: Int -> Logger
logScreen i = withWindowSet $ return . screenFun
    where screenFun x = if length (S.screens x) > 1 then Just $ format char
                                                    else Nothing
              where format = f (isScreenFocused x i)
                    f True  = xmobarColor "#f8f9fa" "#cc342b"
                    f False = xmobarColor myActiveColor ""
          --char = "\xf111"
          --char = "\xf10c"
          --char = "\xf1db"
          --char = "·"
          char = "\xf192"


taskbar :: Int -> Logger
taskbar i = withWindowSet taskFun
    where taskFun x = mapM (formatWindows screenFocus) . stack . workspace $ myScreen
              where myScreen = getScreen x i
                    screenFocus = isScreenFocused x i

          formatWindows screenFocus s@S.Stack {S.focus=focus} = fmt
              where windows = S.integrate s
                    fmt = intercalate sep <$> mapM (winFmt screenFocus focus) windows

          winFmt focusScreen focus x = f <$> getName x
              where f = color focusScreen focusWin . wrap " " " " . shorten 80 . show
                    focusWin = focus == x

          color True  True  = xmobarColor "#f8f9fa" "#cc342b"
          color False True  = xmobarColor myActiveColor ""
          color _     False = xmobarColor myInactiveColor ""

          sep = xmobarColor hiddenColor "" "|"


workspaceLog :: Int -> Logger
workspaceLog i = withWindowSet $ return . Just . wsFun i
    where wsFun i x = fun myWorkspaces
              where myWs = tag $ S.workspace $ getScreen x i
                    colorer = zipWith (addColor focusedScreen) wsStates
                    fun = intercalate sep . colorer . map wsNamer
                    focusedScreen = isScreenFocused x i
                    wsStates = map (getWsState myWs (S.workspaces x)) myWorkspaces

          hasWindows xs = isEmpty . S.stack . (`workspaceByTag` xs)
          isEmpty = not . null . S.integrate'
          workspaceByTag a = head . filter ((== a) . S.tag)

          addColor True  0 = xmobarColor "#f8f9fa" "#cc342b"
          addColor False 0 = xmobarColor myActiveColor ""
          addColor _     1 = xmobarColor "#c0c5ce" ""
          addColor _     2 = xmobarColor hiddenColor ""

          getWsState focus xs a
              | a == focus      = 0
              | hasWindows xs a = 1
              | otherwise       = 2

          sep = " "


layoutLog :: Int -> Logger
layoutLog i = withWindowSet $ return . Just . fun i
    where fun i x = fun myScreen
              where myScreen = getScreen x i
                    getTxt = description . S.layout . S.workspace
                    fun = layoutPrinter . getTxt

          layoutNameCleaner = unwords . filter (not . (`elem` toClean)) . words
              where toClean = [ "Simple"
                              , "Simplest"
                              , "Minimize"
                              , "Maximize"
                              , "ImageButtonDeco"
                              , "DefaultDecoration"
                              , "Spacing"
                              , "0"
                              ]

          layoutPrinter = findIcon . layoutNameCleaner
              where findIcon "empty" = ""
                    findIcon x = printf "<icon=%s/%s.xpm/>" iconsDir x
                    iconsDir = "/home/rilla/.xmonad/icons"


wsNamer :: String -> String
wsNamer "1" = "\xf120"
wsNamer "2" = "\xf121"
wsNamer "3" = "\xf085"
wsNamer "7" = "\xf0e0"
wsNamer "9" = "\xf025"
wsNamer "NSP" = ""
wsNamer x = x


myKeys :: KeyConfig
myKeys conf@ XConfig {modMask = modm} = M.fromList $
    [ ((modm, xK_Return), spawn $ terminal conf)
    , ((modm .|. shiftMask, xK_Return), spawn "/home/rilla/bin/light_termite")

    , ((mod1Mask, xK_Return), namedScratchpadAction myScratchpads "terminal")
    , ((mod1Mask, xK_m     ), namedScratchpadAction myScratchpads "mixer")
    , ((mod1Mask, xK_f     ), namedScratchpadAction myScratchpads "files")
    , ((mod1Mask, xK_w     ), namedScratchpadAction myScratchpads "whatsapp")
    , ((mod1Mask, xK_e     ), namedScratchpadAction myScratchpads "telegram")
    , ((mod1Mask, xK_c     ), namedScratchpadAction myScratchpads "calendar")
    , ((mod1Mask, xK_g     ), namedScratchpadAction myScratchpads "profanity")
    , ((mod1Mask, xK_t     ), namedScratchpadAction myScratchpads "top")
    , ((mod1Mask, xK_y     ), namedScratchpadAction myScratchpads "rtop")

    , ((0, xK_Insert), pasteSelection)

    , ((modm,               xK_n), focusDown)
    , ((modm,               xK_p), focusUp)
    , ((modm .|. shiftMask, xK_n), windows swapDown)
    , ((modm .|. shiftMask, xK_p), windows swapUp)

    , ((modm, xK_plus ), resize M.! "+")
    , ((modm, xK_minus), resize M.! "-")
    , ((modm .|. controlMask, xK_h), resize M.! "L")
    , ((modm .|. controlMask, xK_l), resize M.! "R")
    , ((modm .|. controlMask, xK_j), resize M.! "D")
    , ((modm .|. controlMask, xK_k), resize M.! "U")

    , ((modm, xK_j), windowGo D False)
    , ((modm, xK_k), windowGo U False)
    , ((modm, xK_h), windowGo L False)
    , ((modm, xK_l), windowGo R False)

    , ((modm .|. shiftMask, xK_j), move M.! "D")
    , ((modm .|. shiftMask, xK_k), move M.! "U")
    , ((modm .|. shiftMask, xK_h), move M.! "L")
    , ((modm .|. shiftMask, xK_l), move M.! "R")

    , ((modm .|. controlMask, xK_space), switchLayer)

    , ((modm,                 xK_r   ), dmenuRun)
    , ((modm .|. shiftMask,   xK_r   ), uglyDmenuRunRemote)
    , ((modm .|. controlMask, xK_p   ), passmenu)
    , ((modm .|. shiftMask, xK_c     ), kill1)
    , ((modm,               xK_space ), sendMessage NextLayout)
    , ((modm .|. shiftMask, xK_space ), setLayout $ layoutHook conf)
    , ((modm,               xK_z     ), withFocused minimizeWindow)
    , ((modm .|. shiftMask, xK_z     ), unminimize)
    , ((modm,               xK_m     ), toggleMax)

    , ((modm,               xK_t     ), withFocused $ windows . sink)
    , ((modm .|. shiftMask, xK_t     ), untile)

    , ((modm,               xK_comma ), sendMessage (IncMasterN 1))
    , ((modm,               xK_period), sendMessage (IncMasterN (-1)))
    , ((modm,               xK_s     ), toggleSpacing)
    , ((modm,               xK_b     ), sendMessage ToggleStruts)

    , ((modm .|. controlMask, xK_Escape), reloadConfig)
    , ((modm,               xK_Escape), spawn "i3lock -c 282828")
    , ((modm .|. shiftMask, xK_Escape), spawn "oblogout")
    , ((modm .|. shiftMask, xK_Print ), spawn "xfce4-screenshooter")
    , ((modm,               xK_Print ), spawn "xfce4-screenshooter -r")
    , ((0,                  xK_Print ), spawn "scrot")
    , ((modm,               xK_Right ), nextWS')
    , ((modm,               xK_Left  ), prevWS')
    , ((modm .|. shiftMask, xK_Right ), shiftToNext')
    , ((modm .|. shiftMask, xK_Left  ), shiftToPrev')

    , ((0, xF86XK_AudioMute       ), safeSpawn "pamixer" ["-t"])
    , ((0, xF86XK_AudioLowerVolume), safeSpawn "pamixer" ["-d", "5"])
    , ((0, xF86XK_AudioRaiseVolume), safeSpawn "pamixer" ["-i", "5"])
    , ((0, xF86XK_AudioPlay), safeSpawn "/home/rilla/bin/musicctl" ["toggle"])
    , ((0, xF86XK_AudioStop), safeSpawn "/home/rilla/bin/musicctl" ["stop"])
    , ((0, xF86XK_AudioPrev), safeSpawn "/home/rilla/bin/musicctl" ["prev"])
    , ((0, xF86XK_AudioNext), safeSpawn "/home/rilla/bin/musicctl" ["next"])
    , ((modm, xK_q), spawn "/home/rilla/bin/show_current_song")
    , ((modm, xK_0), toggleWS' ["NSP"])
    ]
    ++ wsBinder modm [xK_1 .. xK_9] (workspaces conf)

    where toggleSpacing = toggleWindowSpacingEnabled >> toggleScreenSpacingEnabled
          toggleMax = withFocused (sendMessage . maximizeRestore)
          unminimize = withLastMinimized maximizeWindowAndFocus
          reloadConfig = spawn "xmonad --recompile; xmonad --restart"

          untile = withFocused rectFloatFocused
              where rectFloatFocused focused = action focused >>= windows
                    action = fmap appEndo . doIt
                    doIt = runQuery $ doRectFloat rect
                    rect = RationalRect 0.05 0.05 0.9 0.9

          floatResize = M.fromList [ ("L", (-n, 0))
                                   , ("R", (n, 0))
                                   , ("D", (0, n))
                                   , ("U", (0, -n))
                                   ]
          tillingResize = M.fromList [ ("L", sendMessage Shrink)
                                     , ("R", sendMessage Expand)
                                     , ("D", sendMessage MirrorShrink)
                                     , ("U", sendMessage MirrorExpand)
                                     ]
          floatMove = M.fromList [ ("L", (-n, 0))
                                 , ("R", (n, 0))
                                 , ("D", (0, n))
                                 , ("U", (0, -n))
                                 ]
          tillingMove = M.fromList [ ("L", L)
                                   , ("R", R)
                                   , ("D", D)
                                   , ("U", U)
                                   ]
          incDec = M.fromList [ ("+", (n,n))
                              , ("-", (-n,-n))
                              ]
          n = 10

          resize = M.union lrdu chSize
              where float = fmap (\x -> keysResizeWindow x (0,0)) floatResize
                    lrdu = M.intersectionWith onFloat float tillingResize
                    chSize = fmap (g . f) incDec
                        where f x = keysResizeWindow x (1%2,1%2)
                              g x = onFloat x $ return ()

          move = M.intersectionWith onFloat floating tilling
              where floating = fmap keysMoveWindow floatResize
                    tilling = fmap (`windowSwap` False) tillingMove

          onFloat a b = withFocused $ ifFloat a (const b)
              where ifFloat a b w = isFloat w >>= picker a b w

                    picker a _ w True = a w
                    picker _ b w False = b w

                    isFloat :: Window -> X Bool
                    isFloat w = M.member w . floating <$> gets windowset

          prevWS' = wsCycler greedyView (-1)
          nextWS' = wsCycler greedyView 1
          shiftToPrev' = wsCycler shift (-1)
          shiftToNext' = wsCycler shift 1

          wsCycler f d = wsBy' d >>= windows . f
              where wsBy' = findWorkspace getSortByIndex Next anyExcept
                    anyExcept = let f (Workspace id _ _) = id `notElem` skips
                                in WSIs $ return f
                    skips = ["NSP"]


myMouseBindings XConfig {modMask = modm} = M.fromList bindings
    where bindings = [ ((modm, button1), move)
                     , ((modm, button2), toMaster)
                     , ((modm, button3), resize)
                     ]
          move = mouseDo mouseMoveWindow
          resize = mouseDo mouseResizeWindow
          toMaster = mouseDo return
          mouseDo f w = focus w >> f w >> windows shiftMaster


type WsAction = WorkspaceId -> WindowSet -> WindowSet
type OnHandler a = WsAction -> a -> X()
type KeyBind = ((ButtonMask, KeySym), X ())
type KeyBinder a = KeyMask -> [KeySym] -> [a] -> [KeyBind]
type KeyConfig = XConfig Layout -> M.Map (ButtonMask, KeySym) (X ())

onWs :: OnHandler WorkspaceId
onWs f i = windows $ f i

onScreen :: OnHandler ScreenId
onScreen f i = screenWorkspace i >>= (`whenJust` onWs f)

-- mod-X to move focus
-- mod-shift-X to shift window
-- mod-ctrl-X to copy window
binder :: KeyMask -> OnHandler a -> [WsAction] -> [KeySym] -> [a] -> [KeyBind]
binder modm onX actions ks is = concat $ zipWith zipBind ks is
    where zipBind k i = zipWith (bind k i) mods actions
          bind k i m f = ((modm .|. m, k), onX f i)
          mods = [0, shiftMask, controlMask]

wsBinder :: KeyBinder WorkspaceId
wsBinder modm = binder modm onWs actions
    where actions = [greedyView, shift, copy]

screenBinder :: KeyBinder ScreenId
screenBinder modm = binder modm onScreen actions
    where actions = [view, shift, copy]

addConfMod :: KeyBinder a -> [KeySym] -> [a] -> KeyConfig
addConfMod f is ks XConfig {modMask = modm} = M.fromList $ f modm is ks

keyComb :: KeyConfig -> KeyConfig -> KeyConfig
keyComb f g conf = let a = f conf
                       b = g conf
                   in M.union a b
