{-# LANGUAGE OverloadedStrings   #-}
{- |
Module      : Text.Pandoc.Writers.Docx
Copyright   : Copyright (C) 2012-2024 John MacFarlane
License     : GNU GPL, version 2 or above
Maintainer  : John MacFarlane <jgm@berkeley.edu>

Conversion of table blocks to docx.
-}
module Text.Pandoc.Writers.Docx.Types
  ( EnvProps (..)
  , WriterEnv (..)
  , defaultWriterEnv
  , WriterState (..)
  , defaultWriterState
  , WS
  , ListMarker (..)
  , listMarkerToId
  , pStyleM
  , isStyle
  , setFirstPara
  , withParaProp
  , withParaPropM
  ) where

import Control.Applicative ((<|>))
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.Text (Text)
import Text.Pandoc.Class.PandocMonad (PandocMonad)
import Text.Pandoc.Definition
import Text.Pandoc.MIME (MimeType)
import Text.Pandoc.Writers.Docx.StyleMap
import Text.Pandoc.Writers.OOXML
import Text.Pandoc.XML.Light as XML
import qualified Data.ByteString as B
import qualified Data.Map as M
import qualified Data.Set as Set
import qualified Data.Text as T

data ListMarker = NoMarker
                | BulletMarker
                | CheckboxMarker Bool
                | NumberMarker ListNumberStyle ListNumberDelim Int
                deriving (Show, Read, Eq, Ord)

listMarkerToId :: ListMarker -> Text
listMarkerToId NoMarker = "990"
listMarkerToId BulletMarker = "991"
listMarkerToId (CheckboxMarker False) = "992"
listMarkerToId (CheckboxMarker True) = "993"
listMarkerToId (NumberMarker sty delim n) = T.pack $
  '9' : '9' : styNum : delimNum : show n
  where styNum = case sty of
                      DefaultStyle -> '2'
                      Example      -> '3'
                      Decimal      -> '4'
                      LowerRoman   -> '5'
                      UpperRoman   -> '6'
                      LowerAlpha   -> '7'
                      UpperAlpha   -> '8'
        delimNum = case delim of
                      DefaultDelim -> '0'
                      Period       -> '1'
                      OneParen     -> '2'
                      TwoParens    -> '3'


data EnvProps = EnvProps{ styleElement  :: Maybe Element
                        , otherElements :: [Element]
                        }

instance Semigroup EnvProps where
  EnvProps s es <> EnvProps s' es' = EnvProps (s <|> s') (es ++ es')

instance Monoid EnvProps where
  mempty = EnvProps Nothing []
  mappend = (<>)

data WriterEnv = WriterEnv
  { envTextProperties :: EnvProps
  , envParaProperties :: EnvProps
  , envRTL            :: Bool
  , envListLevel      :: Int
  , envListNumId      :: Int
  , envInDel          :: Bool
  , envInNote         :: Bool
  , envChangesAuthor  :: Text
  , envChangesDate    :: Text
  , envPrintWidth     :: Integer
  , envLang           :: Maybe Text
  , envSectPr         :: Maybe Element
  }

defaultWriterEnv :: WriterEnv
defaultWriterEnv = WriterEnv
  { envTextProperties = mempty
  , envParaProperties = mempty
  , envRTL = False
  , envListLevel = -1
  , envListNumId = 1
  , envInDel = False
  , envInNote = False
  , envChangesAuthor  = "unknown"
  , envChangesDate    = "1969-12-31T19:00:00Z"
  , envPrintWidth     = 1
  , envLang           = Nothing
  , envSectPr         = Nothing
  }


data WriterState = WriterState{
         stFootnotes      :: [Element]
       , stComments       :: [([(Text, Text)], [Inline])]
       , stSectionIds     :: Set.Set Text
       , stExternalLinks  :: M.Map Text Text
       , stImages         :: M.Map FilePath (String, String, Maybe MimeType, B.ByteString)
       , stLists          :: [ListMarker]
       , stExampleId      :: Maybe Int
       , stInsId          :: Int
       , stDelId          :: Int
       , stStyleMaps      :: StyleMaps
       , stFirstPara      :: Bool
       , stNumIdUsed      :: Bool  -- ^ True if the current numId (envListNumId) has been used.
                                   --   Should only be used once, for the first paragraph.
       , stInTable        :: Bool
       , stInList         :: Bool
       , stTocTitle       :: [Inline]
       , stDynamicParaProps :: Set.Set ParaStyleName
       , stDynamicTextProps :: Set.Set CharStyleName
       , stCurId          :: Int
       , stNextFigureNum  :: Int
       , stNextTableNum   :: Int
       }

defaultWriterState :: WriterState
defaultWriterState = WriterState{
        stFootnotes      = defaultFootnotes
      , stComments       = []
      , stSectionIds     = Set.empty
      , stExternalLinks  = M.empty
      , stImages         = M.empty
      , stLists          = [NoMarker]
      , stExampleId      = Nothing
      , stInsId          = 1
      , stDelId          = 1
      , stStyleMaps      = StyleMaps M.empty M.empty
      , stFirstPara      = False
      , stNumIdUsed      = False
      , stInTable        = False
      , stInList         = False
      , stTocTitle       = [Str "Table of Contents"]
      , stDynamicParaProps = Set.empty
      , stDynamicTextProps = Set.empty
      , stCurId          = 20
      , stNextFigureNum  = 1
      , stNextTableNum   = 1
      }

setFirstPara :: PandocMonad m => WS m ()
setFirstPara =  modify $ \s -> s { stFirstPara = True }

type WS m = ReaderT WriterEnv (StateT WriterState m)

-- Word will insert these footnotes into the settings.xml file
-- (whether or not they're visible in the document). If they're in the
-- file, but not in the footnotes.xml file, it will produce
-- problems. So we want to make sure we insert them into our document.
defaultFootnotes :: [Element]
defaultFootnotes = [ mknode "w:footnote"
                     [("w:type", "separator"), ("w:id", "-1")]
                     [ mknode "w:p" []
                       [mknode "w:r" []
                        [ mknode "w:separator" [] ()]]]
                   , mknode "w:footnote"
                     [("w:type", "continuationSeparator"), ("w:id", "0")]
                     [ mknode "w:p" []
                       [ mknode "w:r" []
                         [ mknode "w:continuationSeparator" [] ()]]]]

pStyleM :: (PandocMonad m) => ParaStyleName -> WS m XML.Element
pStyleM styleName = do
  pStyleMap <- gets (smParaStyle . stStyleMaps)
  let sty' = getStyleIdFromName styleName pStyleMap
  return $ mknode "w:pStyle" [("w:val", fromStyleId sty')] ()

withParaProp :: PandocMonad m => Element -> WS m a -> WS m a
withParaProp d p =
  local (\env -> env {envParaProperties = ep <> envParaProperties env}) p
  where ep = if isStyle d then EnvProps (Just d) [] else EnvProps Nothing [d]

withParaPropM :: PandocMonad m => WS m Element -> WS m a -> WS m a
withParaPropM md p = do
  d <- md
  withParaProp d p

isStyle :: Element -> Bool
isStyle e = isElem [] "w" "rStyle" e ||
            isElem [] "w" "pStyle" e
