{-# LANGUAGE TemplateHaskell #-}
-- | Functions and types to build 'BicolorTable's.  Everything you should
-- typically need is exported from "Rainbox".
module Rainbox.BicolorTable where

import Control.Lens
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Rainbow
import qualified Rainbow.Types as R
import Rainbox.Core
import System.IO

-- | A single line within a cell in a 'BicolorTable'.  For each
-- 'Chunk', leave the 'back' as the default if you want the 'Chunk'
-- background to match '_bctEvenBackground' or '_bctOddBackground'.
-- If you specify a background color for any 'Chunk', it will for
-- that 'Chunk' override the table's background color.
type BicolorTableCellLine = Seq Chunk

-- | The set of all lines within a cell in a 'BicolorTable'.
type BicolorTableCell = Seq BicolorTableCellLine

-- | The set of all columns in a single row.  The length of each
-- 'BicolorTableRow' must be equal to the length of
-- '_bctAlignments'; otherwise, 'bicolorTable' will fail with an
-- error message.
type BicolorTableRow = Seq BicolorTableCell

-- | Description for a table with rows of alternating background colors.  For
-- instance, if designed for a terminal with a white background, the
-- row backgrounds might alternate between white and light grey.
-- The different backgrounds help with readability.
--
-- For the 'Chunk' that are in the table, simply leave the 'back'
-- color blank if you wish to use the row's background color.  Upon
-- rendering, 'bicolorTable' will render the 'Chunk' with a
-- background color that matches that of the row.  If you specify a
-- background color for a 'Chunk', it will override the background
-- color for the row.
--
-- Note that a row may contain more than one line of text.
--
-- Unlike tables built with 'tableByRows' or 'tableByColumns', all
-- tables built with 'bicolorTable' will have separator colums
-- between each column.
data BicolorTable = BicolorTable
  { _bctEvenBackground :: Radiant
  -- ^ Background color for all even-numbered rows.  Row numbering
  -- starts with zero.  To use the terminal's default background color, use
  -- 'mempty'.

  , _bctOddBackground :: Radiant
  -- ^ Background color for all odd-colored rows.  Row numbering
  -- starts with zero.  To use the terminal's default background color, use
  -- 'mempty'.

  , _bctSpacerWidth :: Int
  -- ^ The width of each column of spacer cells.

  , _bctAlignments :: Seq (Alignment Vertical)
  -- ^ Specifies the alignment for each column in the table.  The
  -- length of this 'Seq' must be equal to '_bctColumnCount';
  -- otherwise 'bicolorTable' will fail with an error message.

  , _bctRows :: Seq BicolorTableRow
  -- ^ Specifies all the textual and color data for the
  -- BicolorTable.  The outermost 'Seq' is the set of all rows.
  -- These will alternate in background color bewteen
  -- '_bctEvenBackground' and '_bctOddBackground'.  This is a
  -- Russian doll of nested 'Seq'; the type synonyms help explain
  -- the types.
  } deriving Show

makeLenses ''BicolorTable

-- | Creates a bi-color table.  If the number of columns in each
-- 'BicolorTableRow' is not equal to the length of '_bctAlignments',
-- this will return 'Left'; otherwise, returns 'Right' with a 'Box'
-- 'Vertical' that can then be rendered.
bicolorTable :: BicolorTable -> Either String (Box Vertical)
bicolorTable = fmap tableByRows . bicolorToPlainTable

-- | Creates a bi-color table and renders it to the given 'Handle'
-- using 'bicolorTable' and 'hPutBox'.  Any errors from
-- 'bicolorTable' are repored with 'fail'.
hPutBicolorTable :: Handle -> BicolorTable -> IO ()
hPutBicolorTable h tbl = do
  box <- either fail return . bicolorTable $ tbl
  hPutBox h box

-- | Creates a bi-color table and renders it to standard output
-- using 'hPutBicolorTable'.
putBicolorTable :: BicolorTable -> IO ()
putBicolorTable = hPutBicolorTable stdout

-- | Number each row of a 'Seq' with its row number.
numberSeq :: Seq a -> Seq (Int, a)
numberSeq ls = Seq.zip nums ls
  where
    nums = Seq.iterateN (Seq.length ls) succ 0

-- | Convert a 'Chunk' for rendering by substituting the table's
-- row background for the chunk's row background if applicable.
convertChunkForRendering
  :: Radiant
  -- ^ Background for this row
  -> Chunk
  -> Chunk
convertChunkForRendering rad chk
  = chk
  & over (R.scheme . R.style8 . R.back) newBack8
  & over (R.scheme . R.style256 . R.back) newBack256
  where
    newBack8 (R.Color Nothing) = R._color8 rad
    newBack8 x = x
    newBack256 (R.Color Nothing) = R._color256 rad
    newBack256 x = x

-- | Converts a 'BicolorTableCellLine' for rendering.
convertBicolorTableCellLineForRendering
  :: Radiant
  -> BicolorTableCellLine
  -> BicolorTableCellLine
convertBicolorTableCellLineForRendering rad = fmap (convertChunkForRendering rad)

-- | Converts a 'BicolorTableCell' for rendering.
convertBicolorTableCellForRendering
  :: Radiant
  -> BicolorTableCell
  -> BicolorTableCell
convertBicolorTableCellForRendering rad
  = fmap (convertBicolorTableCellLineForRendering rad)


-- | Convert a BicolorTable cell to a plain Cell.  Does all necessary Chunk
-- conversions.
bicolorToPlainCell
  :: Radiant
  -- ^ Appropriate background color
  -> Alignment Vertical
  -- ^ Column alignment
  -> BicolorTableCell
  -> Cell
bicolorToPlainCell rad align bic = Cell rws top align rad
  where
    rws = convertBicolorTableCellForRendering rad bic

-- | Convert a BicolorTable row to a plain Row.  Does all necessary Chunk conversions.
-- Includes spacer cells.
bicolorToPlainRow
  :: Radiant
  -- ^ Background color for even rows
  -> Radiant
  -- ^ Background color for odd rows
  -> Int
  -- ^ Width of spacer cells
  -> Int
  -- ^ Number for this row
  -> Seq (Alignment Vertical)
  -- ^ Column alignments
  -> BicolorTableRow
  -> Either String (Seq Cell)
bicolorToPlainRow bkgdEven bkgdOdd sepWidth colNum aligns columns
  | Seq.length columns /= Seq.length aligns = Left $ "length of row number " ++ show colNum
      ++ ": " ++ show (Seq.length columns)
      ++ " (should be: " ++ show (Seq.length aligns) ++ ")"
  | otherwise = Right row'
  where
    row' = Seq.intersperse spcr
      $ Seq.zipWith (bicolorToPlainCell bkgd) aligns columns
      where
        bkgd | even colNum = bkgdEven
             | otherwise = bkgdOdd
        spcr = separator bkgd sepWidth

-- | Converts a BicolorTable table to a plain table with 'Cell'.  Does all
-- necessary Chunk conversions, and includes spacer cells.
bicolorToPlainTable
  :: BicolorTable
  -> Either String (Seq (Seq Cell))
bicolorToPlainTable (BicolorTable bkgdEven bkgdOdd sepWidth aligns rws)
  = Seq.traverseWithIndex f rws
  where
    f rowIdx = bicolorToPlainRow bkgdEven bkgdOdd sepWidth rowIdx aligns