    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Text.Html.BlockTable
    -- Copyright   :  (c) Andy Gill and OGI, 1999-2001
    -- License     :  BSD-style (see the file libraries/base/LICENSE)
    --
    -- Maintainer  :  Andy Gill <andy@galconn.com>
    -- Stability   :  provisional
    -- Portability :  portable
    --
    -- An Html combinator library
    --
    -----------------------------------------------------------------------------

    module Text.Html.BlockTable (

    -- Datatypes:

          BlockTable,             -- abstract

    -- Contruction Functions:

          single,
          empty,
          above,
          beside,

    -- Investigation Functions:

          getMatrix,
          showsTable,
          showTable,

          ) where

    infixr 4 `beside`
    infixr 3 `above`

    -- These combinators can be used to build formated 2D tables.
    -- The specific target useage is for HTML table generation.

    {-
       Examples of use:

      	> table1 :: BlockTable String
      	> table1 = single "Hello"	+-----+
    					|Hello|
    	  This is a 1x1 cell		+-----+
    	  Note: single has type

    		single :: a -> BlockTable a

    	  So the cells can contain anything.

    	> table2 :: BlockTable String
    	> table2 = single "World"	+-----+
    					|World|
    					+-----+

    	> table3 :: BlockTable String
    	> table3 = table1 %-% table2	+-----%-----+
    					|Hello%World|
    	 % is used to indicate		+-----%-----+
    	 the join edge between
    	 the two Tables.

    	> table4 :: BlockTable String
    	> table4 = table3 %/% table2	+-----+-----+
    					|Hello|World|
    	  Notice the padding on the	%%%%%%%%%%%%%
    	  smaller (bottom) cell to	|World      |
    	  force the table to be a	+-----------+
    	  rectangle.

    	> table5 :: BlockTable String
    	> table5 = table1 %-% table4	+-----%-----+-----+
    					|Hello%Hello|World|
    	  Notice the padding on the	|     %-----+-----+
    	  leftmost cell, again to	|     %World      |
    	  force the table to be a	+-----%-----------+
    	  rectangle.

       Now the table can be rendered with processTable, for example:
    	Main> processTable table5
    	[[("Hello",(1,2)),
    	  ("Hello",(1,1)),
    	  ("World",(1,1))],
    	 [("World",(2,1))]] :: [[([Char],(Int,Int))]]
    	Main>
    -}

    -- ---------------------------------------------------------------------------
    -- Contruction Functions

    -- Perhaps one day I'll write the Show instance
    -- to show boxes aka the above ascii renditions.

    instance (Show a) => Show (BlockTable a) where
          showsPrec _ = showsTable

    type TableI a = [[(a,(Int,Int))]] -> [[(a,(Int,Int))]]

    data BlockTable a = Table (Int -> Int -> TableI a) Int Int

    -- You can create a (1x1) table entry

    single :: a -> BlockTable a
    single a = Table (\ x y r -> [(a,(x+1,y+1))] : r) 1 1

    empty :: BlockTable a
    empty = Table (\ _ _ r -> r) 0 0

    -- You can compose tables, horizonally and vertically

    above  :: BlockTable a -> BlockTable a -> BlockTable a
    beside :: BlockTable a -> BlockTable a -> BlockTable a

    t1 `above` t2 = trans (combine (trans t1) (trans t2) (.))

    t1 `beside` t2 = combine t1 t2 (\ lst1 lst2 r ->
        let
          -- Note this depends on the fact that
          -- that the result has the same number
          -- of lines as the y dimention; one list
          -- per line. This is not true in general
          -- but is always true for these combinators.
          -- I should assert this!
          -- I should even prove this.
          beside' (x:xs) (y:ys) = (x ++ y) : beside' xs ys
          beside' (x:xs) []     = x        : xs ++ r
          beside' []     (y:ys) = y        : ys ++ r
          beside' []     []     =                  r
        in
          beside' (lst1 []) (lst2 []))

    -- trans flips (transposes) over the x and y axis of
    -- the table. It is only used internally, and typically
    -- in pairs, ie. (flip ... munge ... (un)flip).

    trans :: BlockTable a -> BlockTable a
    trans (Table f1 x1 y1) = Table (flip f1) y1 x1

    combine :: BlockTable a
          -> BlockTable b
          -> (TableI a -> TableI b -> TableI c)
          -> BlockTable c
    combine (Table f1 x1 y1) (Table f2 x2 y2) comb = Table new_fn (x1+x2) max_y
        where
          max_y = max y1 y2
          new_fn x y =
             case compare y1 y2 of
              EQ -> comb (f1 0 y)             (f2 x y)
              GT -> comb (f1 0 y)             (f2 x (y + y1 - y2))
              LT -> comb (f1 0 (y + y2 - y1)) (f2 x y)

    -- ---------------------------------------------------------------------------
    -- Investigation Functions

    -- This is the other thing you can do with a Table;
    -- turn it into a 2D list, tagged with the (x,y)
    -- sizes of each cell in the table.

    getMatrix :: BlockTable a -> [[(a,(Int,Int))]]
    getMatrix (Table r _ _) = r 0 0 []

    -- You can also look at a table

    showsTable :: (Show a) => BlockTable a -> ShowS
    showsTable table = shows (getMatrix table)

    showTable :: (Show a) => BlockTable a -> String
    showTable table = showsTable table ""