module Graphics.PDF.Typesetting.Layout (
   Container(..)
 , Width
 , Height
 , VBox(..)
 , ParagraphStyle(..)
 , VerState(..)
 , vglue
 , addTo
 , isOverfull
 , mkContainer
 , strokeVBoxes
 , containerX
 , containerY
 , containerWidth
 , containerHeight
 , containerContentHeight
 , containerContentRightBorder
 , containerContentLeftBorder
 , containerCurrentHeight
 , containerContentRectangle
 , containerParaTolerance
 ) where
import Graphics.PDF.LowLevel.Types
import Graphics.PDF.Typesetting.Breaking
import Graphics.PDF.Draw
import Graphics.PDF.Coordinates
import Graphics.PDF.Shapes(Rectangle(..))
import Graphics.PDF.Typesetting.Box
import Data.List(foldl')
import Data.Maybe(isJust,fromJust)
data VerState s = VerState { baselineskip :: !(PDFFloat,PDFFloat,PDFFloat) 
                           , lineskip :: !(PDFFloat,PDFFloat,PDFFloat) 
                           , lineskiplimit :: !PDFFloat 
                           , currentParagraphStyle :: !s
                           }
                           
data VBox ps s = Paragraph Int [Letter s] !(Maybe ps) !BRState
               | VBox !PDFFloat !PDFFloat !PDFFloat ![VBox ps s] !(Maybe ps)
               | VGlue !PDFFloat !PDFFloat !PDFFloat !(Maybe (PDFFloat,PDFFloat)) !(Maybe ps)
               | SomeVBox !PDFFloat !BoxDimension !AnyBox !(Maybe ps)
notGlue :: VBox ps s -> Bool
notGlue (VGlue _ _ _ _ _) = False
notGlue (Paragraph _ _ _ _) = False
notGlue _ = True
                              
vglue :: Maybe ps
     -> PDFFloat 
     -> PDFFloat 
     -> PDFFloat 
     -> PDFFloat 
     -> PDFFloat 
     -> VBox ps s
vglue s h y z width delta = VGlue h width delta (Just(y,z)) s
instance Show (VBox ps s) where
 show (VBox _ a _ l _) = "(VBox " ++ show a ++ " " ++ show l ++ ")"
 show (VGlue a _ _ _ _) = "(VGlue " ++ show a ++ ")"
 show (Paragraph _ _ _ _) = "(Paragraph)"
 show (SomeVBox _ d t _) = "(SomeVBox " ++ show (boxHeight d) ++ " " ++ show t ++ ")"
instance MaybeGlue (VBox ps s) where
  glueSizeWithRatio (VGlue w _ _ (Just(y,z)) _) r = glueSize w y z r
  glueSizeWithRatio a _ = boxHeight a
  
  glueY (VGlue _ _ _ (Just(y,_)) _)  = y
  glueY _ = 0
  glueZ (VGlue _ _ _ (Just(_,z)) _)  = z
  glueZ _ = 0
instance Box (VBox ps s) where
   boxWidth (Paragraph _ _ _ _) = 0
   boxWidth (VBox w _ _ _ _) = w
   boxWidth (SomeVBox _ d _ _)  = boxWidth d
   boxWidth (VGlue _ w _ _ _)  = w
   boxHeight (Paragraph _ _ _ _) = 0
   boxHeight (VBox _ h _ _ _) = h
   boxHeight (SomeVBox _ d _ _) = boxHeight d
   boxHeight (VGlue h _ _ _ _) = h
   boxDescent (Paragraph _ _ _ _) = 0
   boxDescent (VBox _ _ d _ _) = d
   boxDescent (SomeVBox _ d _ _) = boxDescent d
   boxDescent (VGlue _ _ _ _ _) = 0
instance (ParagraphStyle ps s) => DisplayableBox (VBox ps s) where
        strokeBox (Paragraph _ _ _ _) _ _ = return ()
        strokeBox b@(VBox _ _ _ l _) x y'' = strokeVBoxes l x y'
          where
              y' = y''  boxHeight b
        strokeBox (VGlue h w delta _ (Just style)) x y = 
            if (isJust . interline $ style)
                then
                    (fromJust . interline $ style) $ Rectangle ((x+delta) :+ (yh)) ((x+w+delta) :+ y)
                else
                   return()
        strokeBox (VGlue _ _ _ _ _) _ _ = return ()
        strokeBox (SomeVBox delta _ a _) x y = strokeBox a (x+delta) y
                
type Width = PDFFloat
type Height = PDFFloat
data Container ps s = Container PDFFloat PDFFloat Width PDFFloat PDFFloat PDFFloat PDFFloat PDFFloat [VBox ps s]
mkContainer :: PDFFloat 
            -> PDFFloat 
            -> PDFFloat 
            -> PDFFloat 
            -> PDFFloat 
            -> Container ps s 
mkContainer x y width height tol = Container x y width height 0 0 0 tol []            
containerWidth :: Container ps s -> PDFFloat
containerWidth (Container _ _ w _ _ _ _ _ _) = w
containerParaTolerance :: Container ps s -> PDFFloat
containerParaTolerance (Container _ _ _ _ _ _ _ t _) = t
containerHeight :: Container ps s -> PDFFloat
containerHeight (Container _ _ _ h _ _ _ _ _) = h
containerCurrentHeight :: Container ps s -> PDFFloat
containerCurrentHeight (Container _ _ _ _ ch _ _ _ _) = ch
containerContentHeight :: Container ps s -> PDFFloat
containerContentHeight (Container _ _ _ maxh h y z _ _) = let r = min (dilatationRatio maxh h y z) 2.0 in
 glueSize h y z r
 
containerContentLeftBorder :: Container ps s -> PDFFloat
containerContentLeftBorder (Container _ _ _ _ _ _ _ _ []) = 0.0
containerContentLeftBorder (Container _ _ _ _ _ _ _ _ l) = minimum . map getBoxDelta $ l
   
containerContentRightBorder :: Container ps s -> PDFFloat
containerContentRightBorder (Container _ _ _ _ _ _ _ _ []) = 0.0
containerContentRightBorder (Container _ _ _ _ _ _ _ _ l) = 
 let xmax = maximum . map rightBorder $ l
     rightBorder x = getBoxDelta x + boxWidth x
 in
  xmax
containerX :: Container ps s -> PDFFloat
containerX (Container x _ _ _ _ _ _ _ _) = x
containerY :: Container ps s -> PDFFloat
containerY (Container _ y _ _ _ _ _ _ _) = y
containerContentRectangle :: Container ps s -> Rectangle
containerContentRectangle c = Rectangle ((x+l) :+ (yth)) ((x+r) :+ y)
 where
    x = containerX c
    y = containerY c
    th = containerContentHeight c
    l = containerContentLeftBorder c
    r = containerContentRightBorder c
getInterlineStyle :: ComparableStyle ps => VBox ps s -> VBox ps s -> Maybe ps
getInterlineStyle (VBox _ _ _ _ (Just s)) (SomeVBox _ _ _ (Just s')) | s `isSameStyleAs` s' = Just s
                                                                     | otherwise = Nothing
getInterlineStyle (VBox _ _ _ _ (Just s)) (VBox _ _ _ _ (Just s')) |  s `isSameStyleAs`  s' = Just s
                                                                   | otherwise = Nothing
getInterlineStyle (SomeVBox _ _ _ (Just s)) (SomeVBox _ _ _ (Just s')) |  s `isSameStyleAs`  s' = Just s
                                                                       | otherwise = Nothing
getInterlineStyle (SomeVBox _ _ _ (Just s)) (VBox _ _ _ _ (Just s')) |  s `isSameStyleAs`  s' = Just s
                                                                     | otherwise = Nothing
getInterlineStyle _ _ = Nothing
interlineGlue :: ComparableStyle ps => VerState ps -> VBox ps s -> VBox ps s -> Maybe (VBox ps s, PDFFloat, PDFFloat)
interlineGlue settings a b | notGlue a && notGlue b = 
    let p = boxDescent a
        h = boxHeight b  boxDescent b
        (ba,by,bz) = baselineskip settings
        (lw,ly,lz) = lineskip settings
        li = lineskiplimit settings
        istyle = getInterlineStyle a b
        theWidth = boxWidth a
        theDelta = getBoxDelta a
    in
    if p <= 1000 
        then
            Nothing
        else
            if ba  p  h >= li
                then
                    Just $ (vglue istyle (baph) by bz theWidth theDelta,by,bz)
                else
                    Just $ (vglue istyle lw ly lz theWidth theDelta,ly,lz)
                                  | otherwise = Nothing
addTo :: ComparableStyle ps => VerState ps -> VBox ps s -> Container ps s -> Container ps s
addTo _ line (Container px py w maxh h y z t []) = Container px py w maxh ((boxHeight line)+h) y z t [line]
addTo settings line (Container px py w maxh h y z t l@(a:_)) = 
    case interlineGlue settings a line of
        Nothing ->
          let h' = boxHeight line + h 
              y' = y + glueY line
              z' = z + glueZ line
          in
          Container px py w maxh h' y' z' t (line:l)
        Just (v,ny,nz) ->
          let h' = boxHeight line + h + boxHeight v
              y' = y + ny + glueY line
              z' = z + nz + glueZ line
          in
          Container px py w maxh h' y' z' t (line:v:l)
          
isOverfull :: Container ps s -> Bool
isOverfull (Container _ _ _ maxh h y z _ _) = let r = dilatationRatio maxh h y z
 in
   if r >= bigAdjustRatio then h > maxh else r <= 1
class (ComparableStyle a, Style s) => ParagraphStyle a s | a -> s where
    
    lineWidth :: a 
               -> PDFFloat 
               -> Int 
               -> PDFFloat 
    
    
    linePosition :: a 
                  -> PDFFloat 
                  -> Int 
                  -> PDFFloat 
    
    
    interline :: a 
              -> Maybe (Rectangle -> Draw ()) 
    interline _ = Nothing
    lineWidth _ w _ = w
    linePosition _ _ = const 0.0
    
    
    paragraphChange :: a 
               -> Int 
               -> [Letter s] 
               -> (a,[Letter s]) 
    paragraphChange a _ l = (a,l)
    
    
    paragraphStyle :: a 
                   -> Maybe (Rectangle -> Draw b -> Draw ()) 
    paragraphStyle _ = Nothing
getBoxDelta :: VBox ps s -> PDFFloat
getBoxDelta (Paragraph _ _ _ _) = 0.0
getBoxDelta (VBox _ _ _ _ _) = 0.0
getBoxDelta (VGlue _ _ delta _ _) = delta
getBoxDelta (SomeVBox delta _ _ _) = delta
isSameParaStyle :: ComparableStyle ps => ps -> VBox ps s -> Bool
isSameParaStyle s (Paragraph _ _ (Just s') _) =  s `isSameStyleAs`  s'
isSameParaStyle s (VBox _ _ _ _ (Just s')) =  s `isSameStyleAs`  s'
isSameParaStyle s (VGlue _ _ _ _ (Just s')) =  s `isSameStyleAs`  s'
isSameParaStyle s (SomeVBox _ _ _ (Just s'))  =  s `isSameStyleAs`  s'     
isSameParaStyle _ _ = False
recurseStrokeVBoxes :: (ParagraphStyle ps s) => Int -> [VBox ps s] -> PDFFloat -> PDFFloat -> Draw ()
recurseStrokeVBoxes _ [] _ _  = return ()
recurseStrokeVBoxes _ (Paragraph _ _ _ _:_) _ _ = return ()
recurseStrokeVBoxes nb (a@(VGlue _ _ _ _ _):l) xa y  = do
    let h = boxHeight a
    strokeBox a xa y
    recurseStrokeVBoxes nb l xa (yh)
recurseStrokeVBoxes nb (a:l) xa y = do
    let h = boxHeight a
    strokeBox a xa y
    recurseStrokeVBoxes (nb+1) l xa (yh)
drawWithParaStyle :: (ParagraphStyle ps s) => ps -> [VBox ps s] -> PDFFloat -> PDFFloat -> Draw ()   
drawWithParaStyle style b xa y' = do
    let  (l',l'') = span (isSameParaStyle style) b
         h' = foldl' (\x' ny -> x' + boxHeight ny) 0.0 l'
    if (isJust . paragraphStyle $ style)
      then do
          let xleft = (minimum $ 100000:map getBoxDelta l' ) + xa
              xright = (maximum $ 0:(map (\x -> boxWidth x + getBoxDelta x) l')) + xa
          (fromJust . paragraphStyle $ style) (Rectangle (xleft :+ (y' h')) (xright :+ y')) (recurseStrokeVBoxes 1 l' xa y')
      else
         recurseStrokeVBoxes 1 l' xa y'
    strokeVBoxes l'' xa (y'  h')
strokeVBoxes :: (ParagraphStyle ps s) => [VBox ps s] 
             -> PDFFloat 
             -> PDFFloat 
             -> Draw ()
strokeVBoxes [] _ _ = return ()
strokeVBoxes b@((Paragraph _ _ (Just s') _):_) xa y = drawWithParaStyle s' b xa y 
strokeVBoxes b@((VBox _ _ _ _ (Just s')):_) xa y = drawWithParaStyle s' b xa y 
strokeVBoxes b@((VGlue _ _ _ _ (Just s')):_) xa y = drawWithParaStyle s' b xa y
strokeVBoxes b@((SomeVBox _ _ _ (Just s')):_) xa y = drawWithParaStyle s' b xa y
strokeVBoxes (a:l) xa y = 
    do
        let h = boxHeight a
        strokeBox a xa y
        strokeVBoxes l xa (yh)