module XMonad.Layout.WindowArranger
    ( 
      
      windowArrange
    , windowArrangeAll
    , WindowArrangerMsg (..)
    , WindowArranger
    , memberFromList
    , listFromList
    , diff
    ) where
import XMonad
import qualified XMonad.StackSet as S
import XMonad.Layout.LayoutModifier
import XMonad.Util.XUtils (fi)
import Control.Arrow
import Data.List
windowArrange :: l a -> ModifiedLayout WindowArranger l a
windowArrange = ModifiedLayout (WA True False [])
windowArrangeAll :: l a -> ModifiedLayout WindowArranger l a
windowArrangeAll = ModifiedLayout (WA True True [])
data WindowArrangerMsg = DeArrange
                       | Arrange
                       | IncreaseLeft  Int
                       | IncreaseRight Int
                       | IncreaseUp    Int
                       | IncreaseDown  Int
                       | DecreaseLeft  Int
                       | DecreaseRight Int
                       | DecreaseUp    Int
                       | DecreaseDown  Int
                       | MoveLeft      Int
                       | MoveRight     Int
                       | MoveUp        Int
                       | MoveDown      Int
                       | SetGeometry   Rectangle
                         deriving ( Typeable )
instance Message WindowArrangerMsg
data ArrangedWindow a = WR   (a, Rectangle)
                      | AWR  (a, Rectangle)
                        deriving (Read, Show)
type ArrangeAll = Bool
data WindowArranger a = WA Bool ArrangeAll [ArrangedWindow a] deriving (Read, Show)
instance (Show a, Read a, Eq a) => LayoutModifier WindowArranger a where
    pureModifier (WA True b   []) _ (Just _)               wrs = arrangeWindows b wrs
    pureModifier (WA True b awrs) _ (Just (S.Stack w _ _)) wrs = curry process wrs awrs
        where
          wins         = map fst       *** map awrWin
          update (a,r) = mkNewAWRs b a *** removeAWRs r >>> uncurry (++)
          process      = wins &&&  id  >>> first diff   >>> uncurry update >>>
                         replaceWR wrs >>> putOnTop w   >>> map fromAWR &&& Just . WA True b
    pureModifier _ _ _ wrs = (wrs, Nothing)
    pureMess (WA True b (wr:wrs)) m
        
        | Just (IncreaseRight i) <- fm, (win, Rectangle x y w h) <- fa = res win  x         y        (w + fi i) h
        | Just (IncreaseLeft  i) <- fm, (win, Rectangle x y w h) <- fa = res win (x  fi i) y        (w + fi i) h
        | Just (IncreaseUp    i) <- fm, (win, Rectangle x y w h) <- fa = res win  x        (y  fi i) w        (h + fi i)
        | Just (IncreaseDown  i) <- fm, (win, Rectangle x y w h) <- fa = res win  x         y         w        (h + fi i)
        
        | Just (DecreaseRight i) <- fm, (win, Rectangle x y w h) <- fa = res win (x + fi i) y        (chk  w i) h
        | Just (DecreaseLeft  i) <- fm, (win, Rectangle x y w h) <- fa = res win  x         y        (chk  w i) h
        | Just (DecreaseUp    i) <- fm, (win, Rectangle x y w h) <- fa = res win  x         y         w        (chk h i)
        | Just (DecreaseDown  i) <- fm, (win, Rectangle x y w h) <- fa = res win  x        (y + fi i) w        (chk h i)
        
        | Just (MoveRight     i) <- fm, (win, Rectangle x y w h) <- fa = res win (x + fi i) y         w         h
        | Just (MoveLeft      i) <- fm, (win, Rectangle x y w h) <- fa = res win (x  fi i) y         w         h
        | Just (MoveUp        i) <- fm, (win, Rectangle x y w h) <- fa = res win  x        (y  fi i) w         h
        | Just (MoveDown      i) <- fm, (win, Rectangle x y w h) <- fa = res win  x        (y + fi i) w         h
        where res wi x y w h = Just . WA True b $ AWR (wi,Rectangle x y w h):wrs
              fm             = fromMessage m
              fa             = fromAWR     wr
              chk        x y = fi $ max 1 (fi x  y)
    pureMess (WA t b (wr:wrs)) m
        | Just (SetGeometry   r) <- fromMessage m, (w,_) <- fromAWR wr = Just . WA t b $ AWR (w,r):wrs
    pureMess (WA _ b l) m
        | Just DeArrange <- fromMessage m = Just $ WA False b l
        | Just Arrange   <- fromMessage m = Just $ WA True  b l
        | otherwise                       = Nothing
arrangeWindows :: ArrangeAll -> [(a,Rectangle)] -> ([(a, Rectangle)], Maybe (WindowArranger a))
arrangeWindows b wrs = (wrs, Just $ WA True b (map t wrs))
    where t = if b then AWR else WR
fromAWR :: ArrangedWindow a -> (a, Rectangle)
fromAWR (WR   x) = x
fromAWR (AWR  x) = x
awrWin :: ArrangedWindow a -> a
awrWin = fst . fromAWR
getAWR :: Eq a => a -> [ArrangedWindow a] -> [ArrangedWindow a]
getAWR = memberFromList awrWin (==)
getWR ::  Eq a => a -> [(a,Rectangle)] -> [(a,Rectangle)]
getWR = memberFromList fst (==)
mkNewAWRs :: Eq a => ArrangeAll -> [a] -> [(a,Rectangle)] -> [ArrangedWindow a]
mkNewAWRs b w wrs = map t . concatMap (flip getWR wrs) $ w
    where t = if b then AWR else WR
removeAWRs :: Eq a => [a] -> [ArrangedWindow a] -> [ArrangedWindow a]
removeAWRs = listFromList awrWin notElem
putOnTop :: Eq a => a -> [ArrangedWindow a] -> [ArrangedWindow a]
putOnTop w awrs = awr ++ nawrs
    where awr   = getAWR w awrs
          nawrs = filter ((/=w) . awrWin) awrs
replaceWR :: Eq a => [(a, Rectangle)] -> [ArrangedWindow a] -> [ArrangedWindow a]
replaceWR wrs = foldr r []
    where r x xs
              | WR wr <- x = case fst wr `elemIndex` map fst wrs of
                               Just i  -> (WR $ wrs !! i):xs
                               Nothing -> x:xs
              | otherwise  = x:xs
listFromList :: (b -> c) -> (c -> [a] -> Bool) -> [a] -> [b] -> [b]
listFromList f g l = foldr (h l) []
    where h x y ys = if g (f y) x then y:ys else ys
memberFromList :: (b -> c) -> (c -> a -> Bool) -> a -> [b] -> [b]
memberFromList f g l = foldr (h l) []
    where h x y ys = if g (f y) x then [y] else ys
diff :: Eq a => ([a],[a]) -> ([a],[a])
diff (x,y) = (x \\ y, y \\ x)