{-# LANGUAGE RecordWildCards #-}
module MarXup.LineUp (Tok(..),lineup,mkSpaces) where

import Data.List
import Data.Foldable
import Control.Monad (when)
import Data.Monoid
import Data.Maybe (catMaybes)

import MarXup.Tex

data Tok = Tok {
  startCol :: Int,
  endCol :: Int,
  preSpace :: Float, -- max amount of space which should come before this token, in mu
  render :: TeX,
  postSpace :: Float -- max amount of space which should come after this token, in mu
  }


justIf True x = Just x
justIf _ _ = Nothing

marx True = '!'
marx False = '-'

lineup :: [[Tok]] -> TeX
lineup input = env'' "list" [] [mempty,tex "\\setlength\\leftmargin{1em}"] $ do
  usepkg "polytable" 100 []
  texLn ""
  texLines $ map (("% " ++) . map marx . drop 1 . isIndentTab ) array
  
  texLn "\\item\\relax"
  cmd "ensuremath" $ env "pboxed" $ do
    declColumn Nothing "B"
    forM_ (zip3 allTabStops [(1::Int)..] (drop 1 indentColumns)) $ \(_col,tab,indenting) -> 
      declColumn (justIf (indenting) $ tex $ show (tab-1) ++ "em") (show tab)
    declColumn Nothing "E"
    texLn "%"
    sequence_ $ intersperse (texLn "\\\\") $ map printLine array
  where
    showCol 0 = "B"
    showCol n = show n
    declColumn :: Maybe TeX -> String -> TeX
    declColumn dim c = do
      cmdm "column" (catMaybes [dim]) [tex c,tex "@{}>{}l<{}@{}"]
      return ()

    printLine :: [[Tok]] -> TeX
    printLine xs = do
      forM_ (zip xs [(0::Int)..]) $ \(ts,colName) -> do
         when (not $ null ts) $ do
           cmdn' ">" [showCol colName] []
           braces $ forM_ ts $ \t -> do 
                render t
      cmdn' "<" ["E"] []
      return ()

    -- The input, grouped in lines and columns
    array :: [[[Tok]]]
    array = map (tabify . mkSpaces) input

    -- Is the token preceded by two spaces or starts a line?
    isAligning :: [Tok] -> [(Bool,Tok)]
    isAligning [] = []
    isAligning (x:xs) = (True,x) :
                        [(startCol t2 > 1 + endCol t1,t2) | (t1,t2) <- zip (x:xs) xs]

    -- | The tabstop possibly beginning an indentation? It cannot be
    -- if it both contains a token and is preceded by stuff.
    isIndentTab :: [[Tok]] -> [Bool]
    isIndentTab xs = zipWith (||) nulls (scanl (&&) True nulls) 
      where nulls = map null xs

    -- | Is a tabstop an indentation? (Take the intersection for all lines)
    indentColumns :: [Bool]
    indentColumns = map and $ transpose $ map isIndentTab array

    -- The tab stops in a line
    tabStops :: [Tok] -> [Int]
    tabStops xs = [startCol x | (align,x) <- isAligning xs, align]

    -- all the tab stops
    allTabStops :: [Int]
    allTabStops = sort $ nub $ concatMap tabStops input

    tabify :: [Tok] -> [[Tok]]
    tabify xs = tabify' (isAligning xs) allTabStops

    clearMeta :: [(Bool,Tok)] -> [Tok]
    clearMeta = map snd

    tabify' :: [(Bool,Tok)] -> [Int]-> [[Tok]]
    tabify' [] _ = []
    tabify' xs [] = [clearMeta xs]
    tabify' xs (t:ts) = clearMeta col:tabify' xs' ts
      where (col,xs') = break (\(align,s) -> align && (startCol s >= t)) xs



--- | Transform a list of tokens to move the spacing info into the TeX
-- field of the tokens (spacing goes after the texts)
mkSpaces :: [Tok] -> [Tok]
mkSpaces [] = []
-- mkSpaces ts = [ Tok startCol endCol
--                 0 (tex (show preSpace) <> render <> tex (show postSpace) <> tex "\\;") 0
--               | Tok{..} <- ts]
mkSpaces ts = [ Tok (startCol l) (endCol l) 0
                (render l <>
                 tex ("\\mskip " ++ show (min (postSpace l) (preSpace r)) ++ "mu" )) 0
              | (l,r) <- zip ts (tail ts) ] ++ [last ts]