-- | Regular array data as markdown (MD) tables.
module Music.Theory.Array.MD where

import Data.List {- base -}

import qualified Music.Theory.Array as T {- hmt -}
import qualified Music.Theory.List as T {- hmt -}
import qualified Music.Theory.String as T {- hmt -}

-- | Optional header row then data rows.
type MD_Table t = (Maybe [String],[[t]])

-- | Join second table to right of initial table.
md_table_join :: MD_Table a -> MD_Table a -> MD_Table a
md_table_join (nm,c) (hdr,tbl) =
    let hdr' = fmap (\h -> maybe h (++ h) nm) hdr
        tbl' = map (\(i,r) -> i ++ r) (zip c tbl)
    in (hdr',tbl')

-- | Add a row number column at the front of the table.
md_number_rows :: MD_Table String -> MD_Table String
md_number_rows (hdr,tbl) =
    let hdr' = fmap ("#" :) hdr
        tbl' = map (\(i,r) -> show i : r) (zip [1::Int ..] tbl)
    in (hdr',tbl')

-- | Markdown table, perhaps with header.  Table is in row order.
-- Options are /pad_left/ and /eq_width/.
--
-- > let tbl = [["a","bc","def"],["ghij","klm","no","p"]]
-- > putStrLn$unlines$"": md_table_opt (True,True," ยท ") (Nothing,tbl)
md_table_opt :: (Bool,Bool,String) -> MD_Table String -> [String]
md_table_opt (pad_left,eq_width,col_sep) (hdr,t) =
    let c = transpose (T.make_regular "" (maybe t (:t) hdr))
        nc = length c
        n = let k = map (maximum . map length) c
            in if eq_width then replicate nc (maximum k) else k
        ext k s = if pad_left then T.pad_left ' ' k s else T.pad_right ' ' k s
        jn = intercalate col_sep
        m = jn (map (flip replicate '-') n)
        w = map jn (transpose (zipWith (map . ext) n c))
        d = map T.delete_trailing_whitespace w
    in case hdr of
         Nothing -> T.bracket (m,m) d
         Just _ -> case d of
                     [] -> error "md_table"
                     d0:d' -> d0 : T.bracket (m,m) d'

md_table' :: MD_Table String -> [String]
md_table' = md_table_opt (True,False," ")

-- | 'curry' of 'md_table''.
md_table :: Maybe [String] -> [[String]] -> [String]
md_table = curry md_table'

-- | Variant relying on 'Show' instances.
--
-- > md_table_show Nothing [[1..4],[5..8],[9..12]]
md_table_show :: Show t => Maybe [String] -> [[t]] -> [String]
md_table_show hdr = md_table hdr . map (map show)

-- | Variant in column order (ie. 'transpose').
--
-- > md_table_column_order [["a","bc","def"],["ghij","klm","no"]]
md_table_column_order :: Maybe [String] -> [[String]] -> [String]
md_table_column_order hdr = md_table hdr . transpose

-- | Two-tuple 'show' variant.
md_table_p2 :: (Show a,Show b) => Maybe [String] -> ([a],[b]) -> [String]
md_table_p2 hdr (p,q) = md_table hdr [map show p,map show q]

-- | Three-tuple 'show' variant.
md_table_p3 :: (Show a,Show b,Show c) => Maybe [String] -> ([a],[b],[c]) -> [String]
md_table_p3 hdr (p,q,r) = md_table hdr [map show p,map show q,map show r]

{- | Matrix form, ie. header in both first row and first column, in
each case displaced by one location which is empty.

> let h = (map return "abc",map return "efgh")
> let t = md_matrix "" h (map (map show) [[1,2,3,4],[2,3,4,1],[3,4,1,2]])

>>> putStrLn $ unlines $ md_table' t
- - - - -
  e f g h
a 1 2 3 4
b 2 3 4 1
c 3 4 1 2
- - - - -

-}
md_matrix :: a -> ([a],[a]) -> [[a]] -> MD_Table a
md_matrix nil (r,c) t = md_table_join (Nothing,[nil] : map return r) (Nothing,c : t)

-- | Variant that takes a 'show' function and a /header decoration/ function.
md_matrix_opt :: (a -> String) -> (String -> String) -> ([a],[a]) -> [[a]] -> MD_Table String
md_matrix_opt show_f hd_f nm t =
    let t' = map (map show_f) t
        nm' = T.bimap1 (map (hd_f . show_f)) nm
    in md_matrix "" nm' t'

-- | MD embolden function.
md_embolden :: String -> String
md_embolden x = "__" ++ x ++ "__"

-- | 'md_matrix_opt' with 'show' and markdown /bold/ annotations for header.
-- the header cells are in bold.
md_matrix_bold :: Show a => ([a],[a]) -> [[a]] -> MD_Table String
md_matrix_bold = md_matrix_opt show md_embolden