-- |
-- Module      : Test.Speculate.Utils.PrettyPrint
-- Copyright   : (c) 2016-2019 Rudy Matela
-- License     : 3-Clause BSD  (see the file LICENSE)
-- Maintainer  : Rudy Matela <rudy@matela.com.br>
--
-- This module is part of Speculate.
--
-- A very simple pretty printing library
module Test.Speculate.Utils.PrettyPrint
  ( beside
  , above
  , table
  , spaces
  )
where
-- TODO: Fix somewhat inefficient implementations, i.e.: heavy use of '(++)'.

import Data.List (transpose)
import Data.Char (isSpace)
import Test.Speculate.Utils.List
import Test.LeanCheck ((+|))

-- | Appends two Strings side by side, line by line
--
-- > beside ["asdf\nqw\n","zxvc\nas"] ==
-- >  "asdfzxvc\n\
-- >  \qw  as\n"
beside :: String -> String -> String
beside cs ds = unlines $ zipWith (++) (normalize ' ' css) dss
  where [css,dss] = normalize "" [lines cs,lines ds]

-- | Append two Strings on top of each other, adding line breaks *when needed*.
above :: String -> String -> String
above cs ds = if last cs == '\n' || head ds == '\n'
                then cs ++ ds
                else cs ++ '\n':ds

-- | Formats a table.  Examples:
--
-- > table "l  l  l" [ ["asdf", "qwer",     "zxvc\nzxvc"]
-- >                 , ["0",    "1",        "2"]
-- >                 , ["123",  "456\n789", "3"] ] ==
-- >   "asdf  qwer  zxvc\n\
-- >   \            zxvc\n\
-- >   \0     1     2\n\
-- >   \123   456   3\n\
-- >   \      789\n"
--
-- > table "r  l  l" [ ["asdf", "qwer",     "zxvc\nzxvc"]
-- >                 , ["0",    "1",        "2"]
-- >                 , ["123",  "456\n789", "3"] ] ==
-- >   "asdf  qwer  zxvc\n\
-- >   \            zxvc\n\
-- >   \   0  1     2\n\
-- >   \ 123  456   3\n\
-- >   \      789\n"
--
-- > table "r  r  l" [ ["asdf", "qwer",     "zxvc\nzxvc"]
-- >                 , ["0",    "1",        "2"]
-- >                 , ["123",  "456\n789", "3"] ] ==
-- >   "asdf  qwer  zxvc\n\
-- >   \            zxvc\n\
-- >   \   0     1  2\n\
-- >   \ 123   456  3\n\
-- >   \       789\n"
table :: String -> [[String]] -> String
table s []  = ""
table s sss = unlines
            . map (removeTrailing ' ')
            . map (concat . (+| spaces s))
            . transpose
            . zipWith (`normalizeTo` ' ') (discard  isSpace s)
            . foldr1 (zipWith (++))
            . map (normalize "" . map lines)
            . normalize ""
            $ sss

-- | Fits a list to a certain width by appending a certain value
--
-- > fit ' ' 6 "str" == "str   "
--
-- > fit 0 6 [1,2,3] == [1,2,3,0,0,0]
fit :: a -> Int -> [a] -> [a]
fit x n xs = xs ++ replicate (n - length xs) x

fitR :: a -> Int -> [a] -> [a]
fitR x n xs = replicate (n - length xs) x ++ xs

-- | normalize makes all list the same length by adding a value
--
-- > normalize ["asdf","qw","er"] == normalize ["asdf","qw  ","er  "]
normalize :: a -> [[a]] -> [[a]]
normalize x xs = map (x `fit` maxLength xs) xs

normalizeR :: a -> [[a]] -> [[a]]
normalizeR x xs = map (x `fitR` maxLength xs) xs

normalizeTo :: Char -> a -> [[a]] -> [[a]]
normalizeTo 'l' = normalize
normalizeTo 'r' = normalizeR
normalizeTo _   = error "normalizeTo: unhandled case"

-- | Given a list of lists returns the maximum length
maxLength :: [[a]] -> Int
maxLength = maximum . (0:) . map length

removeTrailing :: Eq a => a -> [a] -> [a]
removeTrailing x = reverse
                 . dropWhile (==x)
                 . reverse

spaces :: String -> [String]
spaces "" = []
spaces s = case takeWhile isSpace s of
             "" ->      spaces (dropWhile isntSpace $ dropWhile isSpace s)
             s' -> s' : spaces (dropWhile isntSpace $ dropWhile isSpace s)

isntSpace :: Char -> Bool
isntSpace = not . isSpace