{-# LANGUAGE CPP #-}

module Test.Framework.Runners.Console.Table (
        Cell(..), Column(..), renderTable
    ) where

import Test.Framework.Utilities

import Text.PrettyPrint.ANSI.Leijen
    ( (<>), char, empty, fill, hcat, line, text, Doc )

data Cell = TextCell Doc
          | SeperatorCell

data Column = Column [Cell]
            | SeperatorColumn

type ColumnWidth = Int

renderTable :: [Column] -> Doc
renderTable = renderColumnsWithWidth . map (\column -> (findColumnWidth column, column))


findColumnWidth :: Column -> Int
findColumnWidth SeperatorColumn = 0
findColumnWidth (Column cells)  = maximum (map findCellWidth cells)

findCellWidth :: Cell -> Int
findCellWidth (TextCell doc) = maximum (0 : map length (lines (shows doc "")))
findCellWidth SeperatorCell  = 0


renderColumnsWithWidth :: [(ColumnWidth, Column)] -> Doc
renderColumnsWithWidth columns
  | all (columnFinished . snd) columns
  = empty
  | otherwise
  = first_cells_str <> line <>
    renderColumnsWithWidth (map (onRight columnDropHead) columns)
  where
    first_cells_str = hcat $ zipWith (uncurry renderFirstColumnCell) columns (eitherSideSeperator (map snd columns))


eitherSideSeperator :: [Column] -> [Bool]
eitherSideSeperator columns = zipWith (||) (False:column_is_seperator) (tail column_is_seperator ++ [False])
  where
    column_is_seperator = map isSeperatorColumn columns

isSeperatorColumn :: Column -> Bool
isSeperatorColumn SeperatorColumn = False
isSeperatorColumn (Column cells)  = case cells of
    []       -> False
    (cell:_) -> isSeperatorCell cell

isSeperatorCell :: Cell -> Bool
isSeperatorCell SeperatorCell = True
isSeperatorCell _             = False


renderFirstColumnCell :: ColumnWidth -> Column -> Bool -> Doc
renderFirstColumnCell column_width (Column cells) _ = case cells of
    []                    -> text $ replicate (column_width + 2) ' '
    (SeperatorCell:_)     -> text $ replicate (column_width + 2) '-'
    (TextCell contents:_) -> char ' ' <> fill column_width contents <> char ' '
renderFirstColumnCell _ SeperatorColumn either_side_seperator
  = if either_side_seperator then char '+' else char '|'

columnFinished :: Column -> Bool
columnFinished (Column cells)  = null cells
columnFinished SeperatorColumn = True

columnDropHead :: Column -> Column
columnDropHead (Column cells)  = Column (drop 1 cells)
columnDropHead SeperatorColumn = SeperatorColumn