{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances  #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.SortedLayout
-- Description :  A layout modifier that sorts a given layout by a list of properties.
-- Copyright   :  (c) 2016 Kurt Dietrich
-- License     :  BSD-style (see xmonad/LICENSE)
--
-- Maintainer  :  kurto@mac.com
-- Stability   :  unstable
-- Portability :  unportable
--
-- A 'LayoutModifier' that sorts the windows in another layout, given a
-- list of properties. The order of properties in the list determines
-- the order of windows in the final layout. Any unmatched windows
-- go to the end of the order.
-----------------------------------------------------------------------------

module XMonad.Layout.SortedLayout
  ( -- *Usage:
    -- $usage
  sorted
  , Property(..)
  ) where

import           XMonad
import           XMonad.Prelude hiding (Const)
import           XMonad.Layout.LayoutModifier
import           XMonad.StackSet              as W
import           XMonad.Util.WindowProperties

-- $usage
-- You can use this module with the following in your
-- @xmonad.hs@:
--
-- > import XMonad.Layout.SortedLayout
--
-- Then edit your @layoutHook@ to sort another layout (in this case, 'XMonad.Layout.Grid.Grid'):
--
-- > myLayout = sorted [ClassName "Firefox", ClassName "URxvt"] Grid
-- > main = xmonad def { layoutHook = myLayout }
--
-- For more detailed instructions on editing the layoutHook see
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial> and
-- "XMonad.Doc.Extending#Editing_the_layout_hook".


-- | Modify a layout using a list of properties to sort its windows.
sorted :: [Property]
       -> l a
       -> ModifiedLayout SortedLayout l a
sorted props = ModifiedLayout . SortedLayout $ props ++ [Const True]

data WindowDescriptor = WindowDescriptor { wdSeqn :: !Integer
                                         , wdProp :: !Property
                                         , wdId   :: !Window
                                         } deriving (Show, Read)

instance Eq WindowDescriptor where
  (==) a b = wdId a == wdId b

instance Ord WindowDescriptor where
  compare a b = compare (wdSeqn a) (wdSeqn b)

newtype SortedLayout a = SortedLayout [Property] deriving (Show, Read)

instance LayoutModifier SortedLayout Window where
    modifyLayout (SortedLayout props) = sortLayout props
    modifierDescription _             = "Sorted"

findMatchingWindows :: Integer -> Property -> [Window] -> X [WindowDescriptor]
findMatchingWindows seqn prop wids =  fmap (fmap (WindowDescriptor seqn prop)) matching  where
  matching = filterM (hasProperty prop) wids

sortLayout :: (LayoutClass l Window)
           => [Property]
           -> W.Workspace WorkspaceId (l Window) Window
           -> Rectangle
           -> X ([(Window, Rectangle)], Maybe (l Window))
sortLayout props (W.Workspace w l r) rect = do
  let wids = W.integrate' r
  sortedWids <- map wdId . nub . sort . concat <$> zipWithM (\s p -> findMatchingWindows s p wids) [0..] props
  let sr = W.differentiate sortedWids
  runLayout (W.Workspace w l sr) rect
