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

module Text.Html.BlockTable (

-- Datatypes:

      BlockTable,             -- abstract

-- Contruction Functions: 

      single,
      above,
      beside,

-- Investigation Functions: 

      getMatrix,
      showsTable,
      showTable,

      ) where

import Prelude

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 p = 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 z -> [(a,(x+1,y+1))] : z) 1 1


-- 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 ""
