{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ViewPatterns #-}

---------------------------------------------------------
-- |
-- Copyright   : (c) 2006-2016, alpheccar.org
-- License     : BSD-style
--
-- Maintainer  : misc@NOSPAMalpheccar.org
-- Stability   : experimental
-- Portability : portable
--
-- Breaking algorithm used to split a paragraph into lines
-- or a text into pages
---------------------------------------------------------
-- #hide
module Graphics.PDF.Typesetting.Breaking (
   Letter(..)
 , formatList
 , infinity
 , createGlyph
 , kernBox
 , glueBox
 , penalty
 , spaceGlueBox
 , hyphenPenalty
 , splitText
 , MaybeGlue(..)
 , defaultBreakingSettings
 , BRState(..)
 , glueSize
 , mkLetter
 , spaceWidth
 , centeredDilatationFactor
 , leftDilatationFactor
 , rightDilatationFactor
 , dilatationRatio
 , badness
 , bigAdjustRatio
 , Justification(..)
 , simplify
 ) where

import Graphics.PDF.LowLevel.Types
import Data.List(minimumBy)
import qualified Data.Map.Strict as M
import Graphics.PDF.Text
import Graphics.PDF.Typesetting.Box
import Data.Maybe(fromJust)
import Graphics.PDF.Fonts.Font hiding(fontSize)
import Graphics.PDF.Typesetting.WritingSystem
import qualified Data.Text as T(Text)
import qualified Text.Hyphenation as H
--import Debug.Trace

data Justification = FullJustification
                   | Centered
                   | LeftJustification
                   | RightJustification
                   deriving(Eq)

-- | Make a letter from any box
mkLetter :: (Show a, Box a, DisplayableBox a) => BoxDimension -- ^ Dimension of the box
         -> Maybe s -- ^ Text style of the box (can use t)
         -> a  -- ^ Box
         -> Letter s
mkLetter d s a = Letter d (AnyBox a) s

-- | A letter which can be anything. Sizes are widths and for glue the dilation and compression factors
-- For the generic letter, height and descent are also provided
data Letter s = Letter BoxDimension !AnyBox !(Maybe s) -- ^ Any box as a letter
              | Glue !PDFFloat !PDFFloat !PDFFloat !(Maybe s) -- ^ A glue with style to know if it is part of the same sentence
              | FlaggedPenalty !PDFFloat !Int !s -- ^ Hyphen point
              | Penalty !Int -- ^ Penalty
              | AGlyph !s !GlyphCode !PDFFloat -- ^ A glyph
              | Kern !PDFFloat !(Maybe s) -- ^ A kern : non dilatable and non breakable glue

class MaybeGlue a where
    glueY :: a -> PDFFloat
    glueZ :: a -> PDFFloat
    glueSizeWithRatio :: a -> PDFFloat -> PDFFloat

instance MaybeGlue (Letter s) where
    glueSizeWithRatio = letterWidth
    glueY (Glue _ y _ _) = y
    glueY _ = 0
    glueZ (Glue _ _ z _) = z
    glueZ _ = 0


-- | Compute glue width with dilation
glueSize :: PDFFloat -> PDFFloat -> PDFFloat -> PDFFloat -> PDFFloat
glueSize w y z r =
          if r >= 0
              then
                  r*y + w
              else
                  r*z + w

letterWidth :: Letter s -- ^ letter
            -> PDFFloat -- ^ Adjustement ratio
            -> PDFFloat -- ^ Width
letterWidth (AGlyph _ _ w) _ = w
letterWidth (Letter dim _ _) _ = boxWidth dim
letterWidth (Glue w yi zi _) r =  glueSize w yi zi r
letterWidth (FlaggedPenalty _ _ _) _ = 0
letterWidth (Penalty _) _ = 0
letterWidth (Kern w _) _ = w

instance Show (Letter s) where
   show (Letter _ a _) = "(Letter " ++ show a ++ ")"
   show (Glue a b c _) = "(Glue " ++ show a ++ " " ++ show b ++ " " ++ show c ++ ")"
   show (FlaggedPenalty a b _) = "(FlaggedPenalty " ++ show a ++ " " ++ show b ++  ")"
   show (Penalty a) = "(Penalty " ++ show a ++ ")"
   show (AGlyph _ t _) = "(Glyph " ++ show t ++ ")"
   show (Kern _ _) = "(Kern)"

type CB a = (PDFFloat,PDFFloat,PDFFloat,Int,a)


class PointedBox s a | a -> s where
    isFlagged :: a -> Bool
    getPenalty :: a -> Int
    isPenalty :: a -> Bool
    letter :: a -> Letter s
    position :: a -> Int
    cumulatedW :: a -> PDFFloat
    cumulatedY :: a -> PDFFloat
    cumulatedZ :: a -> PDFFloat
    isForcedBreak :: a -> Bool

instance PointedBox s (PDFFloat,PDFFloat,PDFFloat,Int,Letter s)  where
    isFlagged (_,_,_,_,FlaggedPenalty _ _ _) = True
    isFlagged _ = False
    isPenalty (_,_,_,_,FlaggedPenalty _ _ _) = True
    isPenalty (_,_,_,_,Penalty _) = True
    isPenalty _ = False
    getPenalty (_,_,_,_,FlaggedPenalty _ p _) = p
    getPenalty (_,_,_,_,Penalty p) = p
    getPenalty _ = 0
    letter (_,_,_,_,a) = a
    position (_,_,_,p,_) = p
    cumulatedW (w,_,_,_,_) = w
    cumulatedY (_,y,_,_,_) = y
    cumulatedZ (_,_,z,_,_) = z
    isForcedBreak (_,_,_,_,FlaggedPenalty _ p _) = p <= (-infinity)
    isForcedBreak (_,_,_,_,Penalty p) = p <= (-infinity)
    isForcedBreak _ = False


instance PointedBox s (ZList s) where
    isPenalty (ZList _ b _) = isPenalty b
    isFlagged (ZList _ b _) = isFlagged b
    letter (ZList _ b _) = letter b
    position (ZList _ b _) = position b
    cumulatedW (ZList _ b _) = cumulatedW b
    cumulatedY (ZList _ b _) = cumulatedY b
    cumulatedZ (ZList _ b _) = cumulatedZ b
    getPenalty (ZList _ b _) = getPenalty b
    isForcedBreak (ZList _ b _) = isForcedBreak b

-- A penalty has no width unless you break on it so it needs a special processing
penaltyWidth :: Letter s -> PDFFloat
penaltyWidth (FlaggedPenalty w _ _) = w
penaltyWidth _ = 0

data BreakNode  =
                 BreakNode  { totalWidth :: !PDFFloat
                            , totalDilatation :: !PDFFloat
                            , totalCompression :: !PDFFloat
                            , demerit :: !PDFFloat
                            , flagged :: !Bool
                            , fitnessValue :: !Int
                            , ratio :: !PDFFloat
                            , previous :: Maybe (Int,Int,Int,BreakNode)
                            }
                            deriving(Show)


dilatationRatio :: PDFFloat -- ^ Maxw
                -> PDFFloat -- ^ Current w
                -> PDFFloat -- ^ y
                -> PDFFloat -- ^ z
                -> PDFFloat -- ^ Dilatation ratio
dilatationRatio maxw w y z =
                if w == maxw
                    then 0.0
                    else if w < maxw
                      then
                          if y > 0.0 then  ((maxw - w) / y) else bigAdjustRatio
                      else
                          if z > 0.0 then  ((maxw - w) / z) else bigAdjustRatio


adjustRatio :: BreakNode
            -> ZList s
            -> PDFFloat
            -> PDFFloat
adjustRatio a l maxw =
    let w = cumulatedW l - totalWidth a + penaltyWidth (letter l)
        y = cumulatedY l - totalDilatation a
        z = cumulatedZ l - totalCompression a
    in
    dilatationRatio maxw w y z

badness :: PDFFloat -> PDFFloat
badness r = if r < (-1) then bigAdjustRatio else 100.0 * abs(r)**3.0

fitness ::  PDFFloat -> Int
fitness r =
 if   r < (-0.5)
  then
      0
  else if r <= (-0.5)
      then
          1
      else
          if r <= 1
              then
                  2
              else
                  3

-- | Breaking algorithm settings 
data BRState = BRState { firstPassTolerance :: !PDFFloat -- ^ Default value 100
                       , secondPassTolerance :: !PDFFloat -- ^ Default value 100
                       , hyphenPenaltyValue :: !Int -- ^ Default value 50
                       , fitness_demerit :: !PDFFloat -- ^ Default value 1000
                       , flagged_demerit :: !PDFFloat -- ^ Default value 1000
                       , line_penalty :: !PDFFloat -- ^ Default value 10
                       , centered :: !Justification -- ^ Default value false
                       , writingSystem :: !WritingSystem
                       }

defaultBreakingSettings :: BRState
defaultBreakingSettings = BRState 100 100 50 1000 1000 10 FullJustification (Latin H.english_US)


computeDemerit :: Bool
               -> BRState
               -> Bool
               -> PDFFloat -- ^ adjust ratio
               -> BreakNode -- ^ Flag for previous
               -> ZList s -- ^ Flag for current
               -> Maybe(PDFFloat,Int) -- ^ Demerit for the breakpoint
computeDemerit force settings sndPass r a z =
    let b = badness r
        p = getPenalty z
        fitness' = fitness r
        tolerance = if sndPass then (secondPassTolerance settings) else (firstPassTolerance settings)
        in
    if (b <= tolerance) || force -- sndPass
        then
            let fld = if isFlagged z && (flagged a) then (flagged_demerit settings) else 0.0
                fid = if fitness' /= (fitnessValue a) then (fitness_demerit settings) else 0.0
                dem = max 1000.0 $ if p >= 0
                            then
                                fid + fld + ((line_penalty settings) + b) ** 2.0 + (fromIntegral p) ** 2.0
                            else if p < 0 && p > (-infinity)
                                then
                                    fid + fld + ((line_penalty settings) + b) ** 2.0 - (fromIntegral p)**2.0
                                else
                                    fid + fld + ((line_penalty settings) + b) ** 2.0
            in
               Just (dem,fitness')
        else
             Nothing

data MaybeCB a = NoCB
               | OneCB !(CB a)
               deriving(Show)

data ZList s = ZList (MaybeCB (Letter s)) (PDFFloat,PDFFloat,PDFFloat,Int,Letter s) [Letter s] deriving(Show)

-- Used for debugging only
--currentLetter :: ZList s -> Letter s
--currentLetter (ZList _ (_,_,_,_,a) _) = a

createZList :: [Letter s] -> ZList s
createZList [] = error "List cannot be empty to create a zipper"
createZList l = ZList NoCB (0,0,0,1,head l) (tail l)

theEnd :: ZList s -> Bool
theEnd (ZList _ _ []) = True
theEnd _ = False

-- | We create a new breakpoint but we get the cumulated dimensions only at the next box following the break
-- since glues and penalties are removed at the beginning of a line
createBreaknode :: Maybe (Int,Int,Int,BreakNode) -> ZList s -> BreakNode
createBreaknode prev a@(ZList _ (_,_,_,_,FlaggedPenalty _ _ _) []) = breakN prev  True a
createBreaknode prev a@(ZList _ (_,_,_,_,Penalty _) []) = breakN prev False a
createBreaknode prev a@(ZList _ (_,_,_,_,Glue _ _ _ _) []) = breakN prev False a
createBreaknode prev a@(ZList _ (_,_,_,_,_) []) = breakN prev False a
createBreaknode prev a@(ZList _ (_,_,_,_,FlaggedPenalty _ p _) _) | p <= infinity = breakN prev True a
createBreaknode prev a@(ZList _ (_,_,_,_,Letter _ _ _) _) = breakN prev False a
createBreaknode prev a@(ZList _ (_,_,_,_,AGlyph _ _ _) _) = breakN prev False a
createBreaknode prev a@(ZList _ (_,_,_,_,Kern _ _) _) = breakN prev False a
createBreaknode prev z =
    let BreakNode a b c d _ e f g = createBreaknode prev (moveRight z) in
    BreakNode a b c d False e f g

breakN :: Maybe (Int,Int,Int,BreakNode)  -> Bool -> ZList s -> BreakNode
breakN prev t a =  let (w,y,z) = getDim a in BreakNode w y z 0.0 t 0 0.0 prev

-- | Get cumulated dimension for following box
getDim :: ZList s -> (PDFFloat,PDFFloat,PDFFloat)
getDim (ZList _ (w,y,z,_,Letter _ _ _) _) =  (w,y,z)
getDim (ZList _ (w,y,z,_,AGlyph _ _ _) _) =  (w,y,z)
getDim (ZList _ (w,y,z,_,Kern _ _) _) =  (w,y,z)
getDim (ZList _ (w,y,z,_,_) []) = (w,y,z)
getDim a = if theEnd a then error "Can't find end of paragraph" else getDim (moveRight a)


moveRight :: ZList s -> ZList s
moveRight (ZList _ c@(w,y,z,p,Glue w' y' z' _) r) =
    let w'' = w + w'
        y''=y+y'
        z''=z+z'
    in
    ZList (OneCB c) (w'',y'',z'',p+1,head r) (tail r)
moveRight (ZList _ c@(w,y,z,p,a) r) =
        let w' = glueSizeWithRatio a 0.0
            w'' = w + w'
        in
        ZList (OneCB c) (w'',y,z,p+1,head r) (tail r)


isFeasibleBreakpoint :: Bool -- ^ Second pass
                     -> ZList s -- ^ Current analyzed box
                     -> Bool -- ^ Result
isFeasibleBreakpoint True (ZList _ (_,_,_,_,FlaggedPenalty _ p _) _) = p < infinity
isFeasibleBreakpoint False (ZList _ (_,_,_,_,FlaggedPenalty _ _ _) _) = False
isFeasibleBreakpoint _ (ZList _ (_,_,_,_,Penalty p) _) = p < infinity
isFeasibleBreakpoint _ (ZList NoCB _ _) = False
isFeasibleBreakpoint _ (ZList (OneCB (_,_,_,_,Letter _ _ _)) (_,_,_,_,Glue _ _ _ _) _) = True
isFeasibleBreakpoint _ (ZList (OneCB (_,_,_,_,AGlyph _ _ _)) (_,_,_,_,Glue _ _ _ _) _) = True
isFeasibleBreakpoint _ _ = False




-- Update a feasible breakpoint remembering only the best one
type PossibleBreak = ActiveNodes -- Line, demerit and break node
type ActiveNodes  = M.Map (Int,Int,Int) BreakNode

updateBreak :: BreakNode
            -> BreakNode
            -> BreakNode
updateBreak a b = if demerit a < demerit b then a else b

-- | Check is a break point is possible
-- otherwise, if none is possible and there is only one remaining active point,
-- we force a breakpoint
updateWithNewRIfNoSolution :: Bool
                           -> PDFFloat -- ^ Old r
                           -> ZList s -- ^ Current
                           -> (Int,Int,Int)
                           -> PossibleBreak
                           -> ActiveNodes-- ^ Actives
                           -> (Bool -> PDFFloat -> ActiveNodes -> (PossibleBreak,ActiveNodes))
                           -> (PossibleBreak,ActiveNodes)
updateWithNewRIfNoSolution sndPass r z key newbreak newmap f =
        if isForcedBreak z
          then
             f True r (M.delete key newmap)
          else
             if r < -1
                then let m' = M.delete key newmap
                     in
                     if M.null m' && sndPass then f True (-0.99) m' else (newbreak,m')
                else
                     f False r newmap

--debug b z = "(" ++
--    show (cumulatedW z - totalWidth b + penaltyWidth (letter z)) ++ " " ++
--    show (cumulatedY z - totalDilatation b) ++ " " ++
--    show (cumulatedZ z - totalCompression b) ++ ")"   
--    
--breakTrace sndPass b z r' p = trace ("SndPass :" ++ show sndPass ++ " " 
--   ++ debug b z ++ " " ++ show r' ++ " " 
--   ++ show (currentLetter z) ++ " " 
--   ++ show (position z) ++ " -> " 
--   ++ show p
--   ++ if isFlagged z then " (Flagged)" else ""
--   )          

getNewActiveBreakpoints ::  BRState -> Bool -> (Int -> PDFFloat) -> ActiveNodes -> ZList s -> (PossibleBreak,ActiveNodes)
getNewActiveBreakpoints settings sndPass fmaxw actives z =
    if isFeasibleBreakpoint sndPass z
    then
        let analyzeActive key@(p,line,f) b (newbreak,newmap') =
              let r' = adjustRatio b z (fmaxw (line+1))
              in -- breakTrace sndPass b z r' p $
              updateWithNewRIfNoSolution sndPass r' z key newbreak newmap' $
               \force r newmap -> let dem' = computeDemerit force settings sndPass r b z in
                        case dem' of
                            Nothing -> (newbreak,newmap)
                            Just (d',f') ->
                                      let  b' = createBreaknode (Just (p,line,f,b)) z in
                                      -- We keep only the best new break
                                      (M.insertWith updateBreak (position z,line+1,f') (b' {demerit = d',fitnessValue = f', ratio = r}) newbreak ,newmap)
        in
        let (breaks',actives') = M.foldrWithKey analyzeActive (M.empty,actives) actives
            dmin = minimum . map demerit . M.elems $ breaks'
            nbreaks = M.filter (\x -> demerit x < dmin + (fitness_demerit settings)) breaks'
        in
        if M.null nbreaks
         then
           (breaks' , actives')
         else
           (nbreaks , actives')
    else
       (M.empty,actives )

-- (position, line, fitness) -> (adjust ratio, break position)
genNodeList :: (Int,Int,Int,BreakNode) -> [(PDFFloat,Int,Bool)]
genNodeList (p,_,_,b@(BreakNode _ _ _ _ f _ _ Nothing)) = [(ratio b,p,f)]
genNodeList (p,_,_,b@(BreakNode _ _ _ _ f _ _ (Just _))) = (ratio b,p,f):genNodeList (fromJust . previous $ b)


-- Analyze the boxes to compute breaks
analyzeBoxes :: BRState -> Bool -> (Int -> PDFFloat) -> ActiveNodes -> ZList s -> ZList s -> [(PDFFloat,Int,Bool)]
analyzeBoxes settings pass fmaxw actives lastz z =
    let getMinBreak b' = (\((xc,yc,zc),w) -> (xc,yc,zc,w)) . minimumBy (\(_,a) (_,b) -> compare (demerit a) (demerit b)) . M.toList $ b'
        (breaks',actives') = getNewActiveBreakpoints settings pass fmaxw actives z
        newActives = M.union (breaks') (actives')
        getRightOrderNodeList = tail . reverse . genNodeList
        getKey (a,b,c,_) = (a,b,c)
        getNode (_,_,_,BreakNode a b c d e f r _) = BreakNode a b c d e f r Nothing
    in
    --  If forced breakpoint or no breakpoint found
    if M.null actives'
        then
            -- If no breakpoint found
            if M.null breaks'
                then
                    -- Second pass analysis
                    if not pass
                     then
                          analyzeBoxes settings True fmaxw actives lastz lastz
                     else
                          error "Second pass analysis failed ! Generally due to wrong width in the text area or an end of text before end of paragraph detected"
                else
                    -- Forced breakpoint then we gen a list from it and continue the analysis
                    let minBreak = getMinBreak breaks'
                        someNewBreaks = getRightOrderNodeList minBreak
                    in
                    if theEnd z
                      then
                        someNewBreaks
                      else
                        let z' = moveRight z in
                        someNewBreaks ++ analyzeBoxes settings pass fmaxw (M.insert (getKey minBreak) (getNode minBreak) M.empty) z' z'
        -- Normal feasible breakpoint
        else
            if M.null breaks'
             then
                 if theEnd z
                    then
                        error "End of text found but no paragraph end detected"
                    else
                        analyzeBoxes settings pass fmaxw actives' lastz (moveRight z)
             else
                -- If the end it must be a possible breakpoint
                -- We should NEVER reach this part. The end should always be a forced breakpoint
                if theEnd z
                   then
                     let minBreak = getMinBreak breaks' in
                     getRightOrderNodeList minBreak
                   else
                     -- We continue the analysis
                     analyzeBoxes settings pass fmaxw newActives lastz (moveRight z)

-- | Create an hyphen box
hyphenBox :: Style s => s -> Letter s
hyphenBox s =
  let PDFFont f fontSize = textFont . textStyle $ s
      maybeHyphen = hyphenGlyph f
  in
  case maybeHyphen of
  Just h -> AGlyph s h (glyphWidth f fontSize h)
  Nothing -> Kern 0 Nothing

-- Use a list of breakpoint and adjustement ratios to generate a list of lines. Bool means if the break was done on a flagged penalty (hyphen)
cutList :: Style s => Justification -> [Letter s] -> Int -> [(PDFFloat,Int,Bool)] -> [(PDFFloat,[Letter s],[Letter s])]
cutList _ [] _ _ = []
cutList _ t _ [] = [(0.0,[],t)]
cutList j t c ((ra,ba,fa):l) =
   let (theLine,t') = splitAt (ba-c) t
   in
   if null theLine
      then
         []
      else
         if null t'
             then
                 [(ra,theLine,t)]
             else
                 case head t' of
                     FlaggedPenalty _ _ s -> if not fa
                                               then
                                                  error $ "Breakpoint marked as not flagged but detected as flagged ! Send a bug report ! " ++ show (ra,ba,fa)
                                               else
                                                 (ra,theLine ++ hyphenForJustification j s,t) : cutList j t' ba l
                     _ -> if fa
                            then
                                error $ "Breakpoint marked as flagged but detected as not flagged ! Send a bug report ! " ++ show (ra,ba,fa) ++ " " ++ show theLine ++ " " ++ show t'
                            else
                                (ra,theLine,t) : cutList j t' ba l

-- Compute the breakpoints and generate a list of lines with the adjustement ratios
-- The line are not interpreted. Some additional postprocessing is required
-- for horizontal lines of vertical boxes
formatList :: Style s => BRState -> (Int -> PDFFloat) -> [Letter s] -> [(PDFFloat,[Letter s],[Letter s])]
formatList settings maxw boxes =
    let active = M.insert (0,0,1) (BreakNode 0 0 0 0 False 0 0.0 Nothing) M.empty
        z = createZList boxes
        theBreaks = analyzeBoxes settings False maxw active z z
    in
    cutList (centered settings) boxes 1 theBreaks

-- | Value modeling infinity  
infinity :: Int
infinity = 10000

bigAdjustRatio :: PDFFloat
bigAdjustRatio = 10000.0

-- | Add a glue to the stream
glueBox :: Maybe s
        -> PDFFloat -- ^ Glue width
        -> PDFFloat -- ^ Glue dilatation
        -> PDFFloat -- ^ Glue compression
        -> Letter s
glueBox s w y z = Glue w y z s

-- | Return the standard space width
spaceWidth :: Style s => s -- ^ The style
           -> PDFFloat
spaceWidth  s =
    let PDFFont f fontSize = (textFont . textStyle $ s)
        ws = glyphWidth f fontSize (spaceGlyph f)
        h = scaleSpace . textStyle $ s
    in
      ws * h

-- | How much dilatation is allowed compred to the space width   
centeredDilatationFactor :: PDFFloat
centeredDilatationFactor = 10.0

-- | How much dilatation is allowed compared to the space width   
leftDilatationFactor :: PDFFloat
leftDilatationFactor = 20.0

-- | How much dilatation is allowed compared to the space width   
rightDilatationFactor :: PDFFloat
rightDilatationFactor = 20.0

-- | Add a glue to the stream
spaceGlueBox :: Style s => BRState -- ^ Paragraph settings
             -> s -- ^ The style
             -> PDFFloat
             -> [Letter s]
spaceGlueBox settings s f =
     let ws = spaceWidth s
         h = scaleSpace . textStyle $ s
         sy = scaleDilatation . textStyle $ s
         sz = scaleCompression . textStyle $ s
         normalW = ws * h
     in
     case (centered settings) of
        FullJustification -> [Glue (normalW) (normalW*sy/2.0*f) (normalW*sz/3.0) (Just s)]
        Centered -> [ Glue 0 (centeredDilatationFactor*normalW) 0 (Just s)
                    , Penalty 0
                    , Glue (normalW) (-2*centeredDilatationFactor*normalW) 0 (Just s)
                    , Kern 0 (Just s)
                    , Penalty infinity
                    , Glue 0 (centeredDilatationFactor*normalW) 0 (Just s)
                    ]
        LeftJustification -> [ Glue 0 (leftDilatationFactor*normalW) 0 (Just s)
                             , Penalty 0
                             , Glue normalW (-leftDilatationFactor*normalW) 0 (Just s)
                             ]
        RightJustification -> [ Glue normalW (-rightDilatationFactor*normalW) 0 (Just s)
                              , Kern 0 (Just s)
                              , Penalty infinity
                              , Glue 0 (rightDilatationFactor*normalW) 0 (Just s)
                              ]


-- | When a paragraph is full and we start a new one we must clean the beginning paragraph and remove what has been left by the
-- broken space
simplify :: [Letter s]
         -> [Letter s]
simplify [] = []
simplify ((Glue _ _ _ _):l) = simplify l
simplify ((FlaggedPenalty _ _ _):l) = simplify l
simplify ((Penalty _):l) = simplify l
simplify l = l

hyphenForJustification :: Style s => Justification -> s -> [Letter s]
hyphenForJustification Centered s = [hyphenBox s,Glue 0 (centeredDilatationFactor*spaceWidth s) 0 (Just s)]
hyphenForJustification LeftJustification s = [hyphenBox s,Glue 0 (leftDilatationFactor*spaceWidth s) 0 (Just s)]
hyphenForJustification _ s = [hyphenBox s]


-- | Add a penalty to the stream
penalty :: Int -- ^ Penalty value
        -> Letter s
penalty p = Penalty p

-- | Create a box containing text
createGlyph :: s -- ^ Char style
            -> GlyphCode
            -> PDFFloat -- ^ Char width
            -> Letter s
createGlyph s c w = AGlyph s c w



{-
  
Breaking a text with specific hyphen handling if it make sense

-}




ripText :: Style s
        => s
        -> BRState
        -> [SpecialChar] -- ^ Special meaning glyph
        -> [Letter s] -- ^ List of chars and char width taking into account kerning
ripText _ _ [] = []
ripText s settings (NormalChar ca:BreakingHyphen:NormalChar cb:l) =
    let PDFFont f fontSize = (textFont . textStyle $ s)
        ga = charGlyph f ca
        gb = charGlyph f cb
        oldKerning = getKern f fontSize ga gb
        la = createGlyph s ga ((glyphWidth f fontSize ga) + oldKerning)
        lb = createGlyph s gb (glyphWidth f fontSize gb)
        maybeH = hyphenGlyph f
    in
    case maybeH of
    Nothing -> la:lb:ripText s settings l
    Just h ->
          let newKerning = getKern f fontSize ga h
              w = glyphWidth f fontSize h - oldKerning + newKerning
          in
          la:hyphenPenalty settings s w:lb:ripText s settings l
ripText s settings (NormalChar ca:NormalChar cb:l) =
    let PDFFont f fontSize = (textFont . textStyle $ s)
        ga = charGlyph f ca
        gb = charGlyph f cb
        k = getKern f fontSize ga gb
        la = createGlyph s ga ((glyphWidth f fontSize ga) + k)
        lb = createGlyph s gb (glyphWidth f fontSize gb)
    in
    la:lb:ripText s settings l
ripText s settings (NormalSpace:l) = (spaceGlueBox settings s 1.0) ++ ripText s settings l
ripText s settings (BiggerSpace:l) = (spaceGlueBox settings s 2.0) ++ ripText s settings l
ripText s settings (BreakingHyphen:l) = ripText s settings l
ripText s settings (NormalChar c:l) =
  let PDFFont f fontSize = (textFont . textStyle $ s)
      g = charGlyph f c
  in
  createGlyph s g (glyphWidth f fontSize g) :ripText s settings l



-- | split a line into boxes and add hyphen where needed
splitText :: Style s => BRState -> s -> T.Text -> [Letter s]
splitText settings f t  =
   let w = writingSystem settings
       special = mapToSpecialGlyphs w t
   in
   ripText f settings special

-- | Create an hyphen penalty
hyphenPenalty :: BRState
              -> s -- ^ Style of future hyphen
              -> PDFFloat -- ^ Size of hyphen taking into account the kerning that was perturbed by the hyphen introduction. The char before the hyphen is now bigger
              -> Letter s
hyphenPenalty settings s w = FlaggedPenalty w (hyphenPenaltyValue settings) s

kernBox :: s -> PDFFloat -> Letter s
kernBox s w = Kern w (Just s)