{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}
module Graphics.Rendering.MiniTypeset.Layout where
import Control.Monad
import Data.IORef
import qualified Data.Map as Map ; import Data.Map (Map)
import GHC.Float
import Graphics.Rendering.TrueType.STB
import Graphics.Rendering.OpenGL as GL hiding ( Height , translate )
import Graphics.Rendering.MiniTypeset.Common
import Graphics.Rendering.MiniTypeset.Box
import Graphics.Rendering.MiniTypeset.FontTexture
import Graphics.Rendering.MiniTypeset.MultiFont
import Graphics.Rendering.MiniTypeset.Render
subSuperSize =  0.66 :: Double
superPos     =  0.27 :: Double
subPos       = -0.16 :: Double
data Document ident
  = Symbol      !Char
  | String      !String
  | Space
  | HorzCat     !VAlign      [Document ident]
  | VertCat     !HAlign      [Document ident]
  | WithColor   !Col        !(Document ident)
  | WithStyle   !BasicStyle !(Document ident)
  | Identified  !ident      !(Document ident)     
  deriving (Eq,Ord,Show)
newtype SizeIndex
  = SizeIndex Int
  deriving (Eq,Ord,Show)
mfgRelBox :: MultiFontGlyph -> Box
mfgRelBox (MFG ftex bufloc) = Box width height 0 0 top bottom 0 lgap where
  vm  = _ftexVM ftex
  hm  = _locHM  bufloc
  top     =          float2Double (ascent  vm)
  bottom  = negate $ float2Double (descent vm)     
  lgap    = float2Double (lineGap vm)
  width   = float2Double (advanceWidth hm)
  height  = 0
data Layout ident style
  = LoutGlyph Pos style Col Char MultiFontGlyph
  | LoutGroup Box   [Layout ident style]
  | LoutOfs   Pos   (Layout ident style)
  | LoutIdent ident (Layout ident style)
  | LoutEmpty
  deriving (Show)
instance Translate (Layout ident style) where
  translate = translateLayout
translateLayout :: Pos -> Layout ident style -> Layout ident style
translateLayout ofs layout = case layout of
  LoutGlyph ofs0 sty col ch mfg -> LoutGlyph (ofs0+ofs) sty col ch mfg   
  LoutOfs   ofs0 lout           -> LoutOfs   (ofs0+ofs) lout             
  _                             -> LoutOfs   ofs layout
renderLayout :: Ord ident => Layout ident style -> Pos -> IO ()
renderLayout lout pos = void (renderLayout' False lout pos)
dryrunLayout :: Ord ident => Layout ident style -> Pos -> IO (Map ident AbsBox)
dryrunLayout lout pos = renderLayout' True lout pos
renderLayout'
  :: forall ident style. Ord ident
  => Bool                     
  -> Layout ident style
  -> Pos
  -> IO (Map ident AbsBox )
renderLayout' dryrun layout pos0 =
  do
    table <- newIORef Map.empty
    _ <- go table pos0 layout
    readIORef table
  where
    go :: IORef (Map ident AbsBox) -> Pos -> Layout ident style -> IO AbsBox
    go !table !pos layout = case layout of
      LoutGlyph ofs _ col _ mfg -> do
        unless dryrun $ renderMFG (pos+ofs) col mfg
        return (AbsBox pos $ mfgRelBox mfg)
      LoutGroup relbox louts -> do
        mapM_ (go table pos) louts
        return (AbsBox pos relbox)
      LoutOfs ofs lout -> go table (pos + ofs) lout
      LoutIdent ident lout -> do
        box <- go table pos lout
        modifyIORef table (Map.insert ident box)
        return box
      LoutEmpty -> return (AbsBox pos emptyBox)
createLayout
  :: forall fontfile style ident. (Ord fontfile, Ord ident)
  => MultiFont fontfile style
  -> Height
  -> Document ident
  -> IO (Layout ident style)
createLayout multifont height doc = do
  (box0,lout0) <- createLayout' multifont height doc
  
  
  let ofs = Pos (_rboxLeftMarg box0) (_rboxTopMarg box0)
  return $ translate ofs lout0
data Cfg = Cfg
  { _currentSize  :: !SizeIndex
  , _currentStyle :: !BasicStyle
  , _currentColor :: !Col
  }
defaultCfg :: Cfg
defaultCfg = Cfg
  { _currentSize  = SizeIndex 0
  , _currentStyle = Regular
  , _currentColor = Col 0 0 0
  }
createLayout'
  :: forall fontfile style ident. (Ord fontfile, Ord ident)
  => MultiFont fontfile style
  -> Height
  -> Document ident
  -> IO (Box, Layout ident style)
createLayout' multifont (Height height) doc = go initialCfg doc where
  styleMap =  (_ufcStyleMap . _mfUserConfig) multifont
  initialCfg = defaultCfg
  sizeHeight :: SizeIndex -> Int
  sizeHeight (SizeIndex n)
    | n == 0  = height
    | n == 1  = round (fromIntegral height * subSuperSize)
    | n == 2  = round (fromIntegral height * subSuperSize * subSuperSize)
    | n == 3  = round (fromIntegral height * subSuperSize * subSuperSize * 0.8)
    | n >  3  = sizeHeight (SizeIndex 3)
  hcat :: Cfg -> VAlign -> [Document ident] -> IO (Box, Layout ident style)
  hcat !cfg !valign docs = case docs of
    []  -> return (emptyBox, LoutEmpty)
    [d] -> go cfg d
    _   -> do
      bls <- mapM (go cfg) docs
      let (boxes,louts) = unzip bls
          (box,aboxes)  = hcatBoxes valign boxes
          offsets       = map _aboxOffset aboxes
      return (box, LoutGroup box (zipWith translate offsets louts))
  vcat :: Cfg -> HAlign -> [Document ident] -> IO (Box, Layout ident style)
  vcat !cfg !halign docs = case docs of
    []  -> return (emptyBox, LoutEmpty)
    [d] -> go cfg d
    _   -> do
      bls <- mapM (go cfg) docs
      let (boxes,louts) = unzip bls
          (box,aboxes)  = vcatBoxes halign boxes
          offsets       = map _aboxOffset aboxes
      return (box, LoutGroup box (zipWith translate offsets louts))
  go :: Cfg -> Document ident -> IO (Box, Layout ident style)
  go cfg doc = case doc of
    WithColor  col doc -> go cfg' doc where cfg' = cfg { _currentColor = col }
    WithStyle  sty doc -> go cfg' doc where cfg' = cfg { _currentStyle = sty }
    Identified uid doc -> do
      (box, lout) <- go cfg doc
      return (box, LoutIdent uid lout)
    Symbol char -> do
      let Cfg size style0 col = cfg
          style = styleMap style0
      mfg@(MFG ftex bufloc) <- lkpMultiFont multifont (sizeHeight size) style char
      let relbox = mfgRelBox mfg
          lout = LoutGlyph (Pos 0 0) style col char mfg
      return (relbox, lout)
    Space -> go cfg (Symbol ' ')
    String chars -> go cfg (HorzCat AlignBottom $ map Symbol chars)
    HorzCat valign docs -> hcat cfg valign docs
    VertCat halign docs -> vcat cfg halign docs