module HaddockDocs (
  Section(..),
  formatSections,

  Para(..),
  formatParas,
  formatParasFragment,

  Span(..),
  formatSpans,

  haddockSection,
  ) where

import Utils

import Data.List as List (lines, words, intersperse)
import Data.Char (isSpace)

data Section = Section Int String [Para]

data Para =
    ParaText           [Span]    -- an ordinary word-wrapped paragraph
  | ParaCode           [[Span]]  -- a @...@ code block
  | ParaVerbatm        [String]  -- a > bird-track verbatim code block
  | ParaTitle          String    -- a title to a subsection eg an example
  | ParaDefItem [Span] [Span]    -- a definition list item
  | ParaListItem       [Span]    -- a itemisted list item

data Span = SpanText      String -- just simple text
          | SpanIdent     String -- hyperlinked Identifiers
          | SpanModule    String -- hyperlinked module
          | SpanEmphasis  String -- emphasised text /.../
          | SpanMonospace [Span] -- monospace text @...@
          | SpanSpace     Int    -- Just a bunch of spaces
--        | SpanURL
--        | SpanImage
  deriving Show

formatSections :: [Section] -> Doc
formatSections =
    vsep
  . map (\(Section level title paras) ->
      if null paras
        then empty
        else comment <+> text (replicate level '*') <+> text title
          $$ comment
          $$ formatParas 77 paras)

formatParasFragment :: [Para] -> Doc
formatParasFragment =
    haddockSection empty
  . concat
  . intersperse [empty]
  . map (formatPara 77)

formatParas :: Int -> [Para] -> Doc
formatParas width =
    haddockSection (char '|')
  . concat
  . intersperse [empty]
  . map (formatPara width)

formatPara :: Int -> Para -> [Doc]
formatPara width para = case para of
  ParaText       spans -> formatSpans 3 width spans
  ParaCode       lines -> [empty, char '@']
                       ++ map (hcat . map (text . formatSpan)) lines
                       ++ [char '@']
  ParaVerbatm     code -> map (\line -> char '>' <+> text line) code
  ParaTitle      title -> [char '*' <+> text title]
  ParaDefItem term def -> let term' = escape (concatMap formatSpan term)
                              indent = length term' + 6
                           in prependToFirstLine (brackets (text term'))
                                (formatSpans indent width def)
  ParaListItem   spans -> prependToFirstLine (char '*')
                            (formatSpans 5 width spans)
  where escape [] = []
        escape (']':cs) = '\\': ']' : escape cs --we must escape ] in def terms
        escape (c:cs)   =        c  : escape cs

formatSpans :: Int -> Int -> [Span] -> [Doc]
formatSpans initialIndent width =
    map (hsep . map text)
  . wrapText initialIndent width
  . words
  . concatMap formatSpan
  . coalesceTextSpans

formatSpan :: Span -> String
formatSpan (SpanText      text) = escapeHaddockSpecialChars text
formatSpan (SpanIdent     text) = "'" ++ text ++ "'"
formatSpan (SpanModule    text) = "\"" ++ text ++ "\""
formatSpan (SpanEmphasis  text) = "/" ++ escapeHaddockSpecialChars text ++ "/"
formatSpan (SpanMonospace spans) = "@" ++ concatMap formatSpan spans ++ "@"
formatSpan (SpanSpace      len) = replicate len ' '

coalesceTextSpans :: [Span] -> [Span]
coalesceTextSpans = coalesce []
  where coalesce texts (SpanText text : spans) = coalesce (text:texts) spans
        coalesce texts (SpanSpace len : spans) = coalesce (text':texts) spans
                                   where text' = replicate len ' '

        coalesce []    (span : spans) = span : coalesce [] spans
        coalesce texts (span : spans) = SpanText (concat (reverse texts))
                                      : span : coalesce [] spans

        coalesce []    [] = []
        coalesce texts [] = [SpanText (concat (reverse texts))]

escapeHaddockSpecialChars :: String -> String
escapeHaddockSpecialChars = escape
  where escape [] = []
        escape ('\'':'s':s:cs) | isSpace s = '\'' : 's' : ' ' : escape cs --often don't need to escape
        escape ('\'':'t':s:cs) | isSpace s = '\'' : 't' : ' ' : escape cs --eg it's & don't
        escape ('\'':'l':'l':s:cs) | isSpace s = '\'' : 'l' : 'l' : s : escape cs --and you'll
        escape (c:cs) | c == '/' || c == '`'
                     || c == '"' || c == '@'
                     || c == '<' || c == '\''
                      = '\\': c : escape cs
        escape ('\226':'\128':'\148':cs) = '-' : '-' : escape cs  -- UTF8 for EM dash
        escape (c:cs) =       c : escape cs

-------------------------------------------------------------------------------
-- Extra pretty printing bits
-------------------------------------------------------------------------------

prependToFirstLine :: Doc -> [Doc] -> [Doc]
prependToFirstLine start [] = []
prependToFirstLine start (line:lines) = (start <+> line) : lines

haddockSection :: Doc -> [Doc] -> Doc
haddockSection start = commentBlock . prependToFirstLine start
