-- |
-- Module      :  System.Dzen.Padding
-- Copyright   :  (c) 2009 Felipe A. Lessa
-- License     :  GPL 3 (see the LICENSE file in the distribution)
--
-- Maintainer  :  felipe.lessa@gmail.com
-- Stability   :  experimental
-- Portability :  semi-portable (uses MPTC and type families)
--
-- This is a handy module with functions for manual and automatic
-- padding. To pad means to force the length of a string to be of a
-- minimum size by adding /padding characters/ on either or both sides
-- of the string (usually spaces). For example, padding the string
-- @\"123\"@ to have length of 10 characters would give us the string
--
-- > "       123"   -- if padding on the left
-- > "123       "   -- if padding on the right
-- > "    123   "   -- if padding on both sides
--
-- We provide two kinds of padding here:
--
-- [manual padding] This is the kind of padding you usually see
-- in other (non-@dzen@) libraries. You give them the type of
-- padding and the minimum length that you want and they will
-- give you back another string. The @pad*@ functions do this
-- with both plain @DString@s and with the output of @Printer@s.
--
-- [automatic padding] This is the same as a \"never shrink\"
-- padding. An automatic padder adjusts its minimum length
-- to be at least the greatest length it has seen so far, which
-- means that an auto-padded @Printer@ will never shrink its
-- size. This is very useful if you don't want everything swinging
-- on your bar everytime the bar is updated.
module System.Dzen.Padding
    (-- * Manual padding
     -- $padWarning
     padL
    ,padR
    ,padC
    ,pad
    ,PadWhere(..)

     -- * Automatic padding
     -- $autoPad
    ,autoPadL
    ,autoPadR
    ,autoPadC
    ,autoPad
    ) where

import System.Dzen.Internal
import System.Dzen.Base

-- $padWarning
--
-- Note that there are commands that generate graphical
-- outputs, such as 'System.Dzen.Graphics.rect', and we can't
-- tell how many \"characters\" a graphic object has.
-- Whenever you apply any of the padding functions below
-- to a @DString@ that contains one of these graphical
-- objects, there will be no padding. Note that colours
-- do not affect padding as they do not have any width
-- (and we don't mistake the command characters with
-- the characters that will be shown).

-- | Pads the given @DString@ or @Printer@ output
--   with spaces to be at least @n@ chars in length
padL :: Transform a => Int -> (a -> a)
padL = pad ' ' PadLeft

-- | Same as 'padL', but insert spaces on the right of the string.
padR :: Transform a => Int -> (a -> a)
padR = pad ' ' PadRight

-- | Same as 'padL', but insert spaces on both sides,
--   trying to keep the original contents in the middle.
padC :: Transform a => Int -> (a -> a)
padC = pad ' ' PadCenter

-- | Generic pad function, padding with any character
--   and in any place.
pad :: Transform a => Char -> PadWhere -> Int -> (a -> a)
pad c w n = transform $ DS . (pad' .) . unDS
    where
      pad' (string, Just k) | k < n =
          (case w of
             PadCenter -> repli (d+m) . string . repli d
             PadLeft   -> repli a . string
             PadRight  -> string . repli a, Just n)
          where a = n-k; (d,m) = a `divMod` 2
                repli = foldr (.) id . flip replicate (c:)
      pad' other = other

-- | Where to add the padding characters.
data PadWhere = PadLeft | PadRight | PadCenter





-- $autoPad
--
-- Automatic padding adjusts the number of padding characters
-- dinamically, increasing the pad everytime the string
-- size is greater than the pad size. For example, if
-- you give @autoPadL 3@ the following strings
--
-- > "1"
-- > "12"
-- > "123"
-- > "1234"
-- > "12345"
-- > "1234"
-- > "12"
-- > ""
--
-- then it will give the following outputs
--
-- > "  1"
-- > " 12"
-- > "123"
-- > "1234"
-- > "12345"
-- > " 1234"
-- > "   12"
-- > "     "
--
-- Using @autoPadC 3@ would give
--
-- > " 1 "
-- > " 12"
-- > "123"
-- > "1234"
-- > "12345"
-- > " 1234"
-- > "  12 "
-- > "     "
--
-- Some notes:
--
-- - If you're lazy you may give an initial number
--   of zero and after some inputs the padding will be fine.
--
-- - If the automatic pad finds out that there is a graphical
--   object at some string, then it will continue trying to
--   pad the next strings. Although normally the strings will
--   all contain graphics or not, we consider that the performance
--   loss is negligible (and we do what the user expects).

-- | Automatic padding for 'padL'.
autoPadL :: Int -> (Printer a -> Printer a)
autoPadL = autoPad ' ' PadLeft

-- | Automatic padding for 'padR'.
autoPadR :: Int -> (Printer a -> Printer a)
autoPadR = autoPad ' ' PadRight

-- | Automatic padding for 'padC'.
autoPadC :: Int -> (Printer a -> Printer a)
autoPadC = autoPad ' ' PadCenter

-- | Generic automatic padding function, analog to 'pad'.
autoPad :: Char -> PadWhere -> Int -> (Printer a -> Printer a)
autoPad c w n pr =
    P $ \st input -> let (output, pr') = unP pr st input
                         s = maybe 0 id $ size output
                     in case s `compare` n of
                          LT -> (pad c w n output, autoPad c w n pr')
                          _  -> (output,           autoPad c w s pr')