content: -- |
  -- Module      : Gauge.Format
  -- Copyright   : (c) 2017 Vincent Hanquez
  -- 
  -- Formatting helpers
  --
  -- shame there's no leftPad package to use. /s
  --
  module Gauge.Format
      ( printNanoseconds
      , printSubNanoseconds
      , tableMarkdown
      , reset
      , green
      , red
      , yellow
      ) where
  
  import Gauge.Time
  import Data.List
  import Data.Word
  import Text.Printf
  import qualified Basement.Terminal.ANSI as ANSI
  import           Basement.Bounded (zn64)
  import           GHC.Exts (toList)
  
  -- | Print a NanoSeconds quantity with a human friendly format
  -- that make it easy to compare different values
  --
  -- Given a separator Char of '_':
  --
  -- 0           -> "             0"
  -- 1000        -> "         1_000"
  -- 1234567     -> "     1_234_567"
  -- 10200300400 -> "10_200_300_400"
  --
  -- Note that the seconds parameters is aligned considered
  -- maximum of 2 characters (i.e. 99 seconds).
  -- 
  printNanoseconds :: Maybe Char -> NanoSeconds -> String
  printNanoseconds thousandSeparator (NanoSeconds absNs) =
      case divSub1000 0 absNs of
          [ns]         -> padLeft maxLength $ printSpace ns
          [ns,us]      -> padLeft maxLength $ addSeparators1000 [printSpace us,print3 ns]
          [ns,us,ms]   -> padLeft maxLength $ addSeparators1000 [printSpace ms,print3 us,print3 ns]
          [ns,us,ms,s] -> padLeft maxLength $ addSeparators1000 [printSpace s,print3 ms,print3 us,print3 ns]
          _            -> error "printNanoSeconds: internal error: invalid format"
    where
      maxLength = 3 + 3 + 3 + 2 + (sepLength * 3)
    
      (addSeparators1000, sepLength) =
          case thousandSeparator of
              Nothing -> (concat, 0)
              Just c  -> (intercalate [c], 1)
  
      printSpace :: Word64 -> String
      printSpace n = printf "%3d" n
      print3 :: Word64 -> String
      print3 n = printf "%03d" n
  
      divSub1000 :: Int -> Word64 -> [Word64]
      divSub1000 n i
          | n == 3    = [i]
          | otherwise =
              let (d,m) = i `divMod` 1000
               in if d == 0 then [m] else m : divSub1000 (n+1) d
  
  printSubNanoseconds :: Maybe Char -> PicoSeconds100 -> String
  printSubNanoseconds ts p =
      printNanoseconds ts ns ++ "." ++ show fragment
    where
      (ns, fragment) = picosecondsToNanoSeconds p
  
  
  -- | Produce a table in markdown
  --
  -- This is handy when wanting to copy paste to a markdown flavor destination.
  tableMarkdown :: String     -- ^ top left corner label
                -> [String]   -- ^ columns labels
                -> [[String]] -- ^ a list of row labels followed by content rows
                -> String     -- ^ the resulting string
  tableMarkdown name cols rows =
      let hdr = "| " ++ intercalate " | " (padList (name : cols)) ++ " |\n"
          sep = "|-" ++ intercalate "-|-" (map (map (const '-')) (padList (name : cols))) ++ "-|\n"
       in hdr ++ sep ++ concatMap printRow (map padList rows)
    where
      printRow :: [String] -> String
      printRow l = "| " ++ intercalate " | " l ++ " |\n"
  
      getColN n = map (flip (!!) n) rows
  
      sizeCols :: [Int]
      sizeCols = map (\(i, c) -> maximum $ map length (c : getColN i)) $ zip [0..] (name : cols)
  
      padList l = zipWith padCenter sizeCols l
  
  padLeft :: Int -> String -> String
  padLeft sz s
      | sz <= len = s
      | otherwise = replicate leftPad ' ' ++ s
    where
      len = length s
      leftPad = (sz - len)
  
  padCenter :: Int -> String -> String
  padCenter sz s
      | sz <= len = s
      | otherwise = replicate leftPad ' ' ++ s ++ replicate rightPad ' '
    where
      len = length s
      (leftPad, r) = (sz - len) `divMod` 2
      rightPad = leftPad + r
  
  -- | reset, green, red, yellow ANSI escape
  reset, green, red, yellow :: String
  reset = toList ANSI.sgrReset
  green = toList $ ANSI.sgrForeground (zn64 2) True
  red = toList $ ANSI.sgrForeground (zn64 1) True
  yellow = toList $ ANSI.sgrForeground (zn64 3) True
  