{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, FlexibleContexts #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.AutoMaster
-- Copyright   :  (c) 2009 Ilya Portnov
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Ilya Portnov <portnov84@rambler.ru>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Provides layout modifier AutoMaster. It separates screen in two parts -
-- master and slave. Size of slave area automatically changes depending on
-- number of slave windows.
--
-----------------------------------------------------------------------------

module XMonad.Layout.AutoMaster (
                             -- * Usage
                             -- $usage
                             autoMaster
                            ) where
import Control.Monad

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

-- $usage
-- This module defines layout modifier named autoMaster. It separates
-- screen in two parts - master and slave. Master windows are arranged
-- in one row, in slave area underlying layout is run. Size of slave area
-- automatically increases when number of slave windows is increasing.
--
-- You can use this module by adding folowing in your @xmonad.hs@:
--
-- > import XMonad.Layout.AutoMaster
--
-- Then add layouts to your layoutHook:
--
-- > myLayoutHook = autoMaster 1 (1/100) Grid ||| ...
--
-- In this example, master area by default contains 1 window (you can
-- change this number in runtime with usual IncMasterN message), changing
-- slave area size with 1/100 on each Shrink/Expand message.

-- | Data type for layout modifier
data AutoMaster a = AutoMaster Int Float Float
    deriving (Read,Show)

instance LayoutModifier AutoMaster Window where
    modifyLayout (AutoMaster k bias _) = autoLayout k bias
    pureMess = autoMess

-- | Handle Shrink/Expand and IncMasterN messages
autoMess :: AutoMaster a -> SomeMessage -> Maybe (AutoMaster a)
autoMess (AutoMaster k bias delta) m = msum [fmap resize (fromMessage m),
                                             fmap incmastern (fromMessage m)]
    where incmastern (IncMasterN d) = AutoMaster (max 1 (k+d)) bias delta
          resize Expand = AutoMaster k (min ( 0.4)  $ bias+delta) delta
          resize Shrink = AutoMaster k (max (-0.4)  $ bias-delta) delta

-- | Main layout function
autoLayout :: (LayoutClass l Window) =>
              Int ->
              Float ->
              W.Workspace WorkspaceId (l Window) Window
              -> Rectangle
              -> X ([(Window, Rectangle)], Maybe (l Window))
autoLayout k bias wksp rect = do
    let stack = W.stack wksp
    let ws = W.integrate' stack
    let n = length ws
    if null ws then
        runLayout wksp rect
        else do
          if (n<=k) then
              return ((divideRow rect ws),Nothing)
              else do
              let master = take k ws
              let filtStack = stack >>= W.filter (\w -> not (w `elem` master))
              wrs <- runLayout (wksp {W.stack = filtStack}) (slaveRect rect n bias)
              return ((divideRow (masterRect rect n bias) master) ++ (fst wrs),
                      snd wrs)

-- | Calculates height of master area, depending on number of windows.
masterHeight :: Int -> Float -> Float
masterHeight n bias = (calcHeight n) + bias
    where calcHeight :: Int -> Float
          calcHeight 1 = 1.0
          calcHeight m = if (m<9) then (43/45) - (fromIntegral m)*(7/90) else (1/3)

-- | Rectangle for master area
masterRect :: Rectangle -> Int -> Float -> Rectangle
masterRect (Rectangle sx sy sw sh) n bias = Rectangle sx sy sw h
    where h = round $ (fromIntegral sh)*(masterHeight n bias)

-- | Rectangle for slave area
slaveRect :: Rectangle -> Int -> Float -> Rectangle
slaveRect (Rectangle sx sy sw sh) n bias = Rectangle sx (sy+mh) sw h
    where mh = round $ (fromIntegral sh)*(masterHeight n bias)
          h  = round $ (fromIntegral sh)*(1-masterHeight n bias)

-- | Divide rectangle between windows
divideRow :: Rectangle -> [a] -> [(a, Rectangle)]
divideRow (Rectangle x y w h) ws = zip ws rects
    where n = length ws
          oneW = fromIntegral w `div` n
          oneRect = Rectangle x y (fromIntegral oneW) h
          rects = take n $ iterate (shiftR (fromIntegral oneW)) oneRect

-- | Shift rectangle right
shiftR :: Position -> Rectangle -> Rectangle
shiftR s (Rectangle x y w h) = Rectangle (x+s) y w h

-- | User interface function
autoMaster :: LayoutClass l a =>
              Int ->      -- Number of master windows
              Float ->    -- Step for which to increment/decrement master area size with Shrink/Expand
              l a ->
              ModifiedLayout AutoMaster l a
autoMaster nmaster delta = ModifiedLayout (AutoMaster nmaster 0 delta)