{-
    This file is part of funsat.

    funsat is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    funsat is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with funsat.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2008 Denis Bueno
-}

{-|

Tabular output.

Converting any matrix of showable data types into a tabular form for which the
layout is automatically done properly.  Currently there is no maximum row
width, just a dynamically-calculated column width.

If the input matrix is mal-formed, the largest well-formed submatrix is
chosen.  That is, elements along too-long dimensions are chopped off.

-}
module Text.Tabular( Table(..), mkTable, combine, unTable ) where

import Data.List( intercalate )

newtype Table a = Table [Row a]            -- table is a list of rows
newtype Row a = Row [Cell a]
data Cell a = Cell { cellWidth :: !Int
                   -- the width of a cell is the max of the widths of the
                   -- string representations of all the elements in the column
                   -- in which this cell occurs
                   , cellData :: !a } -- element printed in box of colWidth

mkTable :: (Show a) => [[a]] -> Table a
mkTable rows = Table $ mkRows rows
  where
    widths      = colWidths rows
    mkRows rows = [ Row (map mkCell (zip widths row)) | row <- rows ]
    mkCell      = uncurry Cell

unTable :: Table a -> [[a]]
unTable (Table rows) = [ map cellData r | (Row r) <- rows ]

combine :: (Show a) => Table a -> Table a -> Table a
-- slow impl but works
combine t t' = mkTable (unTable t ++ unTable t')

-- returns a list of the widths of each column
colWidths :: (Show a) => [[a]] -> [Int]
colWidths = map (maximum . map (length . show)) . zipn

-- Pretty, columnar output.
instance (Show a) => Show (Table a) where
    show (Table rows) = intercalate "\n" $ map showRow rows 
        where
          showRow (Row cols) = intercalate " " $ colStrings
            where
              colStrings = [ padString (cellWidth c) (show d)
                             | c@(Cell {cellData=d}) <- cols ]

padString maxWidth str = str ++ replicate padLen ' '
    where padLen = maxWidth - length str

zipn xss | any null xss = []
zipn xss = map head xss : zipn (map tail xss)