{-# LANGUAGE NoImplicitPrelude #-}
module Text.Pandoc.Readers.Docx.Lists ( blocksToBullets
                                      , blocksToDefinitions
                                      , listParagraphDivs
                                      ) where
import Prelude
import Data.List
import Data.Maybe
import Text.Pandoc.Generic (bottomUp)
import Text.Pandoc.JSON
import Text.Pandoc.Shared (trim)
isListItem :: Block -> Bool
isListItem (Div (_, classes, _) _) | "list-item" `elem` classes = True
isListItem _                       = False
getLevel :: Block -> Maybe Integer
getLevel (Div (_, _, kvs) _) =  read <$> lookup "level" kvs
getLevel _                   = Nothing
getLevelN :: Block -> Integer
getLevelN b = fromMaybe (-1) (getLevel b)
getNumId :: Block -> Maybe Integer
getNumId (Div (_, _, kvs) _) =  read <$> lookup "num-id" kvs
getNumId _                   = Nothing
getNumIdN :: Block -> Integer
getNumIdN b = fromMaybe (-1) (getNumId b)
getText :: Block -> Maybe String
getText (Div (_, _, kvs) _) = lookup "text" kvs
getText _                   = Nothing
data ListType = Itemized | Enumerated ListAttributes
listStyleMap :: [(String, ListNumberStyle)]
listStyleMap = [("upperLetter", UpperAlpha),
                ("lowerLetter", LowerAlpha),
                ("upperRoman", UpperRoman),
                ("lowerRoman", LowerRoman),
                ("decimal", Decimal)]
listDelimMap :: [(String, ListNumberDelim)]
listDelimMap = [("%1)", OneParen),
                ("(%1)", TwoParens),
                ("%1.", Period)]
getListType :: Block -> Maybe ListType
getListType b@(Div (_, _, kvs) _) | isListItem b =
  let
    start = lookup "start" kvs
    frmt = lookup "format" kvs
    txt  = lookup "text" kvs
  in
   case frmt of
     Just "bullet" -> Just Itemized
     Just f        ->
       case txt of
         Just t -> Just $ Enumerated (
                  read (fromMaybe "1" start) :: Int,
                  fromMaybe DefaultStyle (lookup f listStyleMap),
                  fromMaybe DefaultDelim (lookup t listDelimMap))
         Nothing -> Nothing
     _ -> Nothing
getListType _ = Nothing
listParagraphDivs :: [String]
listParagraphDivs = ["ListParagraph"]
handleListParagraphs :: [Block] -> [Block]
handleListParagraphs [] = []
handleListParagraphs (
  Div attr1@(_, classes1, _) blks1 :
  Div (ident2, classes2, kvs2) blks2 :
  blks
  ) | "list-item" `elem` classes1 &&
    notElem "list-item" classes2 &&
    (not . null) (listParagraphDivs `intersect` classes2) =
      
      let newDiv2 =
            Div (ident2, classes2, filter (\kv -> fst kv /= "indent") kvs2) blks2
      in
       handleListParagraphs (Div attr1 (blks1 ++ [newDiv2]) : blks)
handleListParagraphs (blk:blks) = blk : handleListParagraphs blks
separateBlocks' :: Block -> [[Block]] -> [[Block]]
separateBlocks' blk [[]] = [[blk]]
separateBlocks' b@(BulletList _) acc = init acc ++ [last acc ++ [b]]
separateBlocks' b@(OrderedList _ _) acc = init acc ++ [last acc ++ [b]]
separateBlocks' b acc | fmap trim (getText b) == Just "" =
  init acc ++ [last acc ++ [b]]
separateBlocks' b acc = acc ++ [[b]]
separateBlocks :: [Block] -> [[Block]]
separateBlocks blks = foldr separateBlocks' [[]] (reverse blks)
flatToBullets' :: Integer -> [Block] -> [Block]
flatToBullets' _ [] = []
flatToBullets' num xs@(b : elems)
  | getLevelN b == num = b : flatToBullets' num elems
  | otherwise =
    let bNumId = getNumIdN b
        bLevel = getLevelN b
        (children, remaining) =
          span
          (\b' ->
            getLevelN b' > bLevel ||
             (getLevelN b' == bLevel && getNumIdN b' == bNumId))
          xs
    in
     case getListType b of
       Just (Enumerated attr) ->
         OrderedList attr (separateBlocks $ flatToBullets' bLevel children) :
         flatToBullets' num remaining
       _ ->
         BulletList (separateBlocks $ flatToBullets' bLevel children) :
         flatToBullets' num remaining
flatToBullets :: [Block] -> [Block]
flatToBullets elems = flatToBullets' (-1) elems
singleItemHeaderToHeader :: Block -> Block
singleItemHeaderToHeader (OrderedList _ [[h@Header{}]]) = h
singleItemHeaderToHeader blk                            = blk
blocksToBullets :: [Block] -> [Block]
blocksToBullets blks =
  map singleItemHeaderToHeader $
  bottomUp removeListDivs $flatToBullets (handleListParagraphs blks)
plainParaInlines :: Block -> [Inline]
plainParaInlines (Plain ils) = ils
plainParaInlines (Para ils)  = ils
plainParaInlines _           = []
blocksToDefinitions' :: [([Inline], [[Block]])] -> [Block] -> [Block] -> [Block]
blocksToDefinitions' []     acc [] = reverse acc
blocksToDefinitions' defAcc acc [] =
  reverse $ DefinitionList (reverse defAcc) : acc
blocksToDefinitions' defAcc acc
  (Div (_, classes1, _) blks1 : Div (ident2, classes2, kvs2) blks2 : blks)
  | "DefinitionTerm" `elem` classes1 && "Definition"  `elem` classes2 =
    let remainingAttr2 = (ident2, delete "Definition" classes2, kvs2)
        pair = if remainingAttr2 == ("", [], []) then (concatMap plainParaInlines blks1, [blks2]) else (concatMap plainParaInlines blks1, [[Div remainingAttr2 blks2]])
    in
     blocksToDefinitions' (pair : defAcc) acc blks
blocksToDefinitions' ((defTerm, defItems):defs) acc
  (Div (ident2, classes2, kvs2) blks2 : blks)
  | "Definition"  `elem` classes2 =
    let remainingAttr2 = (ident2, delete "Definition" classes2, kvs2)
        defItems2 = case remainingAttr2 == ("", [], []) of
          True  -> blks2
          False -> [Div remainingAttr2 blks2]
        defAcc' = case null defItems of
          True -> (defTerm, [defItems2]) : defs
          False -> (defTerm, init defItems ++ [last defItems ++ defItems2]) : defs
    in
     blocksToDefinitions' defAcc' acc blks
blocksToDefinitions' [] acc (b:blks) =
  blocksToDefinitions' [] (b:acc) blks
blocksToDefinitions' defAcc acc (b:blks) =
  blocksToDefinitions' [] (b : DefinitionList (reverse defAcc) : acc) blks
removeListDivs' :: Block -> [Block]
removeListDivs' (Div (ident, classes, kvs) blks)
  | "list-item" `elem` classes =
    case delete "list-item" classes of
      []       -> blks
      classes' -> [Div (ident, classes', kvs) blks]
removeListDivs' (Div (ident, classes, kvs) blks)
  | not $ null $ listParagraphDivs `intersect` classes =
    case classes \\ listParagraphDivs of
      []       -> blks
      classes' -> [Div (ident, classes', kvs) blks]
removeListDivs' blk = [blk]
removeListDivs :: [Block] -> [Block]
removeListDivs = concatMap removeListDivs'
blocksToDefinitions :: [Block] -> [Block]
blocksToDefinitions = blocksToDefinitions' [] []