module Text.Layout.Table.Primitives.ColumnModifier where

import Control.Arrow ((&&&))
import Data.List

import Text.Layout.Table.Cell
import Text.Layout.Table.Primitives.AlignInfo
import Text.Layout.Table.Spec.AlignSpec
import Text.Layout.Table.Spec.ColSpec
import Text.Layout.Table.Spec.CutMark
import Text.Layout.Table.Spec.LenSpec
import Text.Layout.Table.Spec.OccSpec
import Text.Layout.Table.Spec.Position
import Text.Layout.Table.Spec.Util
import Text.Layout.Table.StringBuilder

-- | Specifies how a column should be modified. Values of this type are derived
-- in a traversal over the input columns by using 'deriveColModInfos'. Finally,
-- 'columnModifier' will interpret them and apply the appropriate modification
-- function to the cells of the column.
data ColModInfo
    = FillAligned OccSpec AlignInfo
    | FillTo Int
    | FitTo Int (Maybe (OccSpec, AlignInfo))

-- | Private show function.
showCMI :: ColModInfo -> String
showCMI :: ColModInfo -> String
showCMI ColModInfo
cmi = case ColModInfo
cmi of
    FillAligned OccSpec
_ AlignInfo
ai -> String
"FillAligned .. " String -> String -> String
forall a. [a] -> [a] -> [a]
++ AlignInfo -> String
showAI AlignInfo
ai
    FillTo Int
i         -> String
"FillTo " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
i
    FitTo Int
i Maybe (OccSpec, AlignInfo)
_        -> String
"FitTo " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
i String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".."

-- | Get the exact width of a 'ColModInfo' after applying it with
-- 'columnModifier'.
widthCMI :: ColModInfo -> Int
widthCMI :: ColModInfo -> Int
widthCMI ColModInfo
cmi = case ColModInfo
cmi of
    FillAligned OccSpec
_ AlignInfo
ai -> AlignInfo -> Int
widthAI AlignInfo
ai
    FillTo Int
maxLen    -> Int
maxLen
    FitTo Int
lim Maybe (OccSpec, AlignInfo)
_      -> Int
lim

-- | Remove alignment from a 'ColModInfo'. This is used to change alignment of
-- headers while using the combined width information.
unalignedCMI :: ColModInfo -> ColModInfo
unalignedCMI :: ColModInfo -> ColModInfo
unalignedCMI ColModInfo
cmi = case ColModInfo
cmi of
    FillAligned OccSpec
_ AlignInfo
ai -> Int -> ColModInfo
FillTo (Int -> ColModInfo) -> Int -> ColModInfo
forall a b. (a -> b) -> a -> b
$ AlignInfo -> Int
widthAI AlignInfo
ai
    FitTo Int
i Maybe (OccSpec, AlignInfo)
_        -> Int -> Maybe (OccSpec, AlignInfo) -> ColModInfo
FitTo Int
i Maybe (OccSpec, AlignInfo)
forall a. Maybe a
Nothing
    ColModInfo
_                -> ColModInfo
cmi

-- | Ensures that the modification provides a minimum width but only if it is
-- not limited.
ensureWidthCMI :: Int -> Position H -> ColModInfo -> ColModInfo
ensureWidthCMI :: Int -> Position H -> ColModInfo -> ColModInfo
ensureWidthCMI Int
w Position H
pos ColModInfo
cmi = case ColModInfo
cmi of
    FillAligned OccSpec
oS ai :: AlignInfo
ai@(AlignInfo Int
lw Maybe Int
optRW)
                  ->
        let neededW :: Int
neededW = Int
w Int -> Int -> Int
forall a. Num a => a -> a -> a
- AlignInfo -> Int
widthAI AlignInfo
ai
        in if Int
neededW Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0
           then ColModInfo
cmi
           else OccSpec -> AlignInfo -> ColModInfo
FillAligned OccSpec
oS (AlignInfo -> ColModInfo) -> AlignInfo -> ColModInfo
forall a b. (a -> b) -> a -> b
$ case Position H
pos of
               Position H
Start  -> case Maybe Int
optRW of
                   Just Int
rw -> Int -> Maybe Int -> AlignInfo
AlignInfo Int
lw (Maybe Int -> AlignInfo) -> Maybe Int -> AlignInfo
forall a b. (a -> b) -> a -> b
$ Int -> Maybe Int
forall a. a -> Maybe a
Just (Int
rw Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
neededW)
                   Maybe Int
Nothing -> Int -> Maybe Int -> AlignInfo
AlignInfo (Int
lw Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
neededW) Maybe Int
optRW
               Position H
End    -> Int -> Maybe Int -> AlignInfo
AlignInfo (Int
lw Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
neededW) Maybe Int
optRW
               Position H
Center -> case Maybe Int
optRW of
                   Just Int
_  -> let (Int
q, Int
r) = Int
w Int -> Int -> (Int, Int)
forall a. Integral a => a -> a -> (a, a)
`divMod` Int
2 
                              -- Calculate a new distribution.
                              in Int -> Maybe Int -> AlignInfo
AlignInfo Int
q (Maybe Int -> AlignInfo) -> Maybe Int -> AlignInfo
forall a b. (a -> b) -> a -> b
$ Int -> Maybe Int
forall a. a -> Maybe a
Just (Int
q Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
r)
                   Maybe Int
Nothing -> Int -> Maybe Int -> AlignInfo
AlignInfo (Int
lw Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
neededW) Maybe Int
optRW
    FillTo Int
maxLen -> Int -> ColModInfo
FillTo (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
maxLen Int
w)
    ColModInfo
_             -> ColModInfo
cmi

-- | Ensures that the given 'String' will fit into the modified columns.
ensureWidthOfCMI :: String -> Position H -> ColModInfo -> ColModInfo
ensureWidthOfCMI :: String -> Position H -> ColModInfo -> ColModInfo
ensureWidthOfCMI = Int -> Position H -> ColModInfo -> ColModInfo
ensureWidthCMI (Int -> Position H -> ColModInfo -> ColModInfo)
-> (String -> Int)
-> String
-> Position H
-> ColModInfo
-> ColModInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Int
forall a. Cell a => a -> Int
visibleLength

-- | Fit titles of a header column into the derived 'ColModInfo'.
fitTitlesCMI :: [String] -> [Position H] -> [ColModInfo] -> [ColModInfo]
fitTitlesCMI :: [String] -> [Position H] -> [ColModInfo] -> [ColModInfo]
fitTitlesCMI = (String -> Position H -> ColModInfo -> ColModInfo)
-> [String] -> [Position H] -> [ColModInfo] -> [ColModInfo]
forall a b c d. (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
zipWith3 String -> Position H -> ColModInfo -> ColModInfo
ensureWidthOfCMI

-- | Generates a function which modifies a given cell according to
-- 'Text.Layout.Table.Position.Position', 'CutMark' and 'ColModInfo'. This is
-- used to modify a single cell of a column to bring all cells of a column to
-- the same width.
columnModifier
    :: (Cell a, StringBuilder b)
    => Position H
    -> CutMark
    -> ColModInfo
    -> (a -> b)
columnModifier :: Position H -> CutMark -> ColModInfo -> a -> b
columnModifier Position H
pos CutMark
cms ColModInfo
colModInfo = case ColModInfo
colModInfo of
    FillAligned OccSpec
oS AlignInfo
ai -> OccSpec -> AlignInfo -> a -> b
forall a b.
(Cell a, StringBuilder b) =>
OccSpec -> AlignInfo -> a -> b
align OccSpec
oS AlignInfo
ai
    FillTo Int
maxLen     -> Position H -> Int -> a -> b
forall a b o.
(Cell a, StringBuilder b) =>
Position o -> Int -> a -> b
pad Position H
pos Int
maxLen
    FitTo Int
lim Maybe (OccSpec, AlignInfo)
mT      ->
        (a -> b)
-> ((OccSpec, AlignInfo) -> a -> b)
-> Maybe (OccSpec, AlignInfo)
-> a
-> b
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Position H -> CutMark -> Int -> a -> b
forall a b o.
(Cell a, StringBuilder b) =>
Position o -> CutMark -> Int -> a -> b
trimOrPad Position H
pos CutMark
cms Int
lim) ((OccSpec -> AlignInfo -> a -> b) -> (OccSpec, AlignInfo) -> a -> b
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry ((OccSpec -> AlignInfo -> a -> b)
 -> (OccSpec, AlignInfo) -> a -> b)
-> (OccSpec -> AlignInfo -> a -> b)
-> (OccSpec, AlignInfo)
-> a
-> b
forall a b. (a -> b) -> a -> b
$ Position H -> CutMark -> Int -> OccSpec -> AlignInfo -> a -> b
forall a b o.
(Cell a, StringBuilder b) =>
Position o -> CutMark -> Int -> OccSpec -> AlignInfo -> a -> b
alignFixed Position H
pos CutMark
cms Int
lim) Maybe (OccSpec, AlignInfo)
mT

-- | Derive the 'ColModInfo' by using layout specifications and the actual cells
-- of a column. This function only needs to know about 'LenSpec' and 'AlignInfo'.
deriveColModInfos :: Cell a => [(LenSpec, AlignSpec)] -> [Row a] -> [ColModInfo]
deriveColModInfos :: [(LenSpec, AlignSpec)] -> [Row a] -> [ColModInfo]
deriveColModInfos [(LenSpec, AlignSpec)]
specs = ((Row a -> ColModInfo) -> Row a -> ColModInfo)
-> [Row a -> ColModInfo] -> [Row a] -> [ColModInfo]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (Row a -> ColModInfo) -> Row a -> ColModInfo
forall a b. (a -> b) -> a -> b
($) (((LenSpec, AlignSpec) -> Row a -> ColModInfo)
-> [(LenSpec, AlignSpec)] -> [Row a -> ColModInfo]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (LenSpec, AlignSpec) -> Row a -> ColModInfo
forall a. Cell a => (LenSpec, AlignSpec) -> [a] -> ColModInfo
fSel [(LenSpec, AlignSpec)]
specs) ([Row a] -> [ColModInfo])
-> ([Row a] -> [Row a]) -> [Row a] -> [ColModInfo]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Row a] -> [Row a]
forall a. [[a]] -> [[a]]
transpose
  where
    fSel :: (LenSpec, AlignSpec) -> [a] -> ColModInfo
fSel (LenSpec
lenS, AlignSpec
alignS) = case AlignSpec
alignS of
        AlignSpec
NoAlign     -> let fitTo :: Int -> b -> ColModInfo
fitTo Int
i              = ColModInfo -> b -> ColModInfo
forall a b. a -> b -> a
const (ColModInfo -> b -> ColModInfo) -> ColModInfo -> b -> ColModInfo
forall a b. (a -> b) -> a -> b
$ Int -> Maybe (OccSpec, AlignInfo) -> ColModInfo
FitTo Int
i Maybe (OccSpec, AlignInfo)
forall a. Maybe a
Nothing
                           expandUntil' :: (Bool -> Bool) -> Int -> Int -> ColModInfo
expandUntil' Bool -> Bool
f Int
i Int
max' = if Bool -> Bool
f (Int
max' Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
i)
                                                   then Int -> ColModInfo
FillTo Int
max'
                                                   else Int -> Int -> ColModInfo
forall b. Int -> b -> ColModInfo
fitTo Int
i Int
max'
                           fun :: Int -> ColModInfo
fun                  = case LenSpec
lenS of
                               LenSpec
Expand        -> Int -> ColModInfo
FillTo
                               Fixed i       -> Int -> Int -> ColModInfo
forall b. Int -> b -> ColModInfo
fitTo Int
i
                               ExpandUntil i -> (Bool -> Bool) -> Int -> Int -> ColModInfo
expandUntil' Bool -> Bool
forall a. a -> a
id Int
i
                               FixedUntil i  -> (Bool -> Bool) -> Int -> Int -> ColModInfo
expandUntil' Bool -> Bool
not Int
i
                       in Int -> ColModInfo
fun (Int -> ColModInfo) -> ([a] -> Int) -> [a] -> ColModInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Int] -> Int) -> ([a] -> [Int]) -> [a] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Int) -> [a] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map a -> Int
forall a. Cell a => a -> Int
visibleLength
        AlignOcc OccSpec
oS -> let fitToAligned :: Int -> AlignInfo -> ColModInfo
fitToAligned Int
i      = Int -> Maybe (OccSpec, AlignInfo) -> ColModInfo
FitTo Int
i (Maybe (OccSpec, AlignInfo) -> ColModInfo)
-> (AlignInfo -> Maybe (OccSpec, AlignInfo))
-> AlignInfo
-> ColModInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (OccSpec, AlignInfo) -> Maybe (OccSpec, AlignInfo)
forall a. a -> Maybe a
Just ((OccSpec, AlignInfo) -> Maybe (OccSpec, AlignInfo))
-> (AlignInfo -> (OccSpec, AlignInfo))
-> AlignInfo
-> Maybe (OccSpec, AlignInfo)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (,) OccSpec
oS
                           fillAligned :: AlignInfo -> ColModInfo
fillAligned         = OccSpec -> AlignInfo -> ColModInfo
FillAligned OccSpec
oS
                           expandUntil' :: (Bool -> Bool) -> Int -> AlignInfo -> ColModInfo
expandUntil' Bool -> Bool
f Int
i AlignInfo
ai = if Bool -> Bool
f (AlignInfo -> Int
widthAI AlignInfo
ai Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
i)
                                                then AlignInfo -> ColModInfo
fillAligned AlignInfo
ai
                                                else Int -> AlignInfo -> ColModInfo
fitToAligned Int
i AlignInfo
ai
                           fun :: AlignInfo -> ColModInfo
fun                = case LenSpec
lenS of
                               LenSpec
Expand        -> AlignInfo -> ColModInfo
fillAligned
                               Fixed i       -> Int -> AlignInfo -> ColModInfo
fitToAligned Int
i
                               ExpandUntil i -> (Bool -> Bool) -> Int -> AlignInfo -> ColModInfo
expandUntil' Bool -> Bool
forall a. a -> a
id Int
i
                               FixedUntil i  -> (Bool -> Bool) -> Int -> AlignInfo -> ColModInfo
expandUntil' Bool -> Bool
not Int
i
                        in AlignInfo -> ColModInfo
fun (AlignInfo -> ColModInfo)
-> ([a] -> AlignInfo) -> [a] -> ColModInfo
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> AlignInfo) -> [a] -> AlignInfo
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap (OccSpec -> a -> AlignInfo
forall a. Cell a => OccSpec -> a -> AlignInfo
deriveAlignInfo OccSpec
oS)

deriveColModInfos' :: Cell a => [ColSpec] -> [Row a] -> [ColModInfo]
deriveColModInfos' :: [ColSpec] -> [Row a] -> [ColModInfo]
deriveColModInfos' = [(LenSpec, AlignSpec)] -> [Row a] -> [ColModInfo]
forall a.
Cell a =>
[(LenSpec, AlignSpec)] -> [Row a] -> [ColModInfo]
deriveColModInfos ([(LenSpec, AlignSpec)] -> [Row a] -> [ColModInfo])
-> ([ColSpec] -> [(LenSpec, AlignSpec)])
-> [ColSpec]
-> [Row a]
-> [ColModInfo]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ColSpec -> (LenSpec, AlignSpec))
-> [ColSpec] -> [(LenSpec, AlignSpec)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ColSpec -> LenSpec
lenSpec (ColSpec -> LenSpec)
-> (ColSpec -> AlignSpec) -> ColSpec -> (LenSpec, AlignSpec)
forall (a :: * -> * -> *) b c c'.
Arrow a =>
a b c -> a b c' -> a b (c, c')
&&& ColSpec -> AlignSpec
alignSpec)

-- | Derive the 'ColModInfo' and generate functions without any intermediate
-- steps.
deriveColMods
    :: (Cell a, StringBuilder b)
    => [ColSpec]
    -> [Row a]
    -> [a -> b]
deriveColMods :: [ColSpec] -> [Row a] -> [a -> b]
deriveColMods [ColSpec]
specs [Row a]
tab =
    ((Position H, CutMark) -> ColModInfo -> a -> b)
-> [(Position H, CutMark)] -> [ColModInfo] -> [a -> b]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith ((Position H -> CutMark -> ColModInfo -> a -> b)
-> (Position H, CutMark) -> ColModInfo -> a -> b
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Position H -> CutMark -> ColModInfo -> a -> b
forall a b.
(Cell a, StringBuilder b) =>
Position H -> CutMark -> ColModInfo -> a -> b
columnModifier) ((ColSpec -> (Position H, CutMark))
-> [ColSpec] -> [(Position H, CutMark)]
forall a b. (a -> b) -> [a] -> [b]
map (ColSpec -> Position H
position (ColSpec -> Position H)
-> (ColSpec -> CutMark) -> ColSpec -> (Position H, CutMark)
forall (a :: * -> * -> *) b c c'.
Arrow a =>
a b c -> a b c' -> a b (c, c')
&&& ColSpec -> CutMark
cutMark) [ColSpec]
specs) [ColModInfo]
cmis
  where
    cmis :: [ColModInfo]
cmis = [ColSpec] -> [Row a] -> [ColModInfo]
forall a. Cell a => [ColSpec] -> [Row a] -> [ColModInfo]
deriveColModInfos' [ColSpec]
specs [Row a]
tab

-- | Generate the 'AlignInfo' of a cell by using the 'OccSpec'.
deriveAlignInfo :: Cell a => OccSpec -> a -> AlignInfo
deriveAlignInfo :: OccSpec -> a -> AlignInfo
deriveAlignInfo OccSpec
occSpec = (Char -> Bool) -> a -> AlignInfo
forall a. Cell a => (Char -> Bool) -> a -> AlignInfo
measureAlignment (OccSpec -> Char -> Bool
predicate OccSpec
occSpec)