{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}

module Clang.Internal.Comment
( ParsedComment(..)
, ParamPassDirection(..)
, parseComment
, getFFIComment
) where

import Control.Monad
import Control.Monad.IO.Class
import Data.Typeable

import qualified Clang.Internal.FFI as FFI
import Clang.Internal.Monad

-- | A node in the comment AST.
--
-- Every constructor contains an opaque 'FFI.Comment' value, which represents the AST node
-- itself. The other fields are the user-visible metadata for that node type.
data ParsedComment s
  -- | Plain text. Inline content.
  = TextComment (FFI.Comment s) (FFI.ClangString s)

  -- | A command with word-like arguments that is considered inline content.
  | InlineCommandComment (FFI.Comment s) (FFI.ClangString s) FFI.InlineCommandRenderStyle [FFI.ClangString s]

  -- | An HTML start tag with attributes (represented as name/value pairs). Considered inline content.
  -- The final argument of the constructor is 'True' if the tag is self-closing.
  | HTMLStartTagComment (FFI.Comment s) (FFI.ClangString s) [(FFI.ClangString s, FFI.ClangString s)] Bool

  -- | An HTML end tag. Considered inline content.
  | HTMLEndTagComment (FFI.Comment s) (FFI.ClangString s)

  -- | A paragraph, which contains inline content. The paragraph itself is block content.
  | ParagraphComment (FFI.Comment s)

  -- | A command which has zero or more word-like arguments and a paragraph argument. Block content.
  -- The paragraph argument (of type 'ParsedComment') is also a child of the 'BlockCommandComment'.
  --
  -- As an example, a \'\\brief\' comment creates a 'BlockCommandComment' AST node with no word-like
  -- arguments and a paragraph argument.
  | BlockCommandComment (FFI.Comment s) (FFI.ClangString s) [FFI.ClangString s] (Maybe (ParsedComment s))

  -- | A \'\\param\' or \'\\arg\' command that describes a function parameter. The parameter name,
  -- index in the parameter list (or 'Nothing' if the index is invalid), and parameter passing
  -- direction are provided as constructor fields. The description is provided as a child node.
  | ParamCommandComment (FFI.Comment s) (FFI.ClangString s) (Maybe Int) ParamPassDirection


  -- | A \'\\tparam\' command that describes a template parameter. The parameter name and position
  -- are provided as constructor fields, while the description is provided as a child node.
  --
  -- Since template parameters can be nested, the position is a list with a left-to-right position
  -- at each nesting depth. For example, for the following declaration:
  --
  -- > template<typename C, template<typename T> class TT>
  -- > void test(TT<int> aaa);
  --
  -- The resulting position would be [0] for \'C\', [1] for \'TT\', and [1, 0] for \'T\'.
  | TypeParamCommandComment (FFI.Comment s) (FFI.ClangString s) (Maybe [Int])

  -- | A verbatim block command (e.g. preformatted code). A verbatim block has an opening
  -- and a closing command and contains multiple lines of text, represented as
  -- 'VerbatimBlockLineComment' nodes.
  | VerbatimBlockCommandComment (FFI.Comment s) (Maybe (ParsedComment s))

  -- | A line of text that is contained within a 'VerbatimBlockCommandComment' node.
  | VerbatimBlockLineComment (FFI.Comment s) (FFI.ClangString s)

  -- | A verbatim line command. A verbatim line has an opening command, a single line
  -- of text (up to the newline after the opening command), and has no closing command.
  | VerbatimLineComment (FFI.Comment s) (FFI.ClangString s)

  -- | A full comment attached to a declaration. Contains block content.
  | FullComment (FFI.Comment s)
    deriving (Eq, Ord, Typeable)

-- | A parameter passing direction, either explicitly provided in the comment text or
-- inferred.
data ParamPassDirection
  = ExplicitParamPassDirection FFI.ParamPassDirectionKind
  | InferredParamPassDirection FFI.ParamPassDirectionKind
    deriving (Eq, Ord, Read, Show, Typeable)

parseComment :: ClangBase m => FFI.Comment s -> ClangT s m (Maybe (ParsedComment s))
parseComment c = do
  kind <- liftIO $ FFI.comment_getKind c
  case kind of
    FFI.NullComment                 -> pure Nothing
    FFI.TextComment                 -> Just . TextComment c <$> FFI.textComment_getText c
    FFI.InlineCommandComment        -> do v <- InlineCommandComment c <$> FFI.inlineCommandComment_getCommandName c
                                                                     <*> inlineCommandComment_getRenderKind c
                                                                     <*> inlineCommandComment_getArgs c
                                          return $ Just v
    FFI.HTMLStartTagComment         -> do v <- HTMLStartTagComment c <$> FFI.hTMLTagComment_getTagName c
                                                                     <*> htmlStartTagComment_getAttrs c
                                                                     <*> htmlStartTagComment_isSelfClosing c
                                          return $ Just v
    FFI.HTMLEndTagComment           -> Just . HTMLEndTagComment c <$> FFI.hTMLTagComment_getTagName c
    FFI.ParagraphComment            -> pure $ Just (ParagraphComment c)
    FFI.BlockCommandComment         -> do v <- BlockCommandComment c <$> FFI.blockCommandComment_getCommandName c
                                                                     <*> blockCommandComment_getArgs c
                                                                     <*> blockCommandComment_getParagraph c
                                          return $ Just v
    FFI.ParamCommandComment         -> do v <- ParamCommandComment c <$> FFI.paramCommandComment_getParamName c
                                                                     <*> paramCommandComment_getParamIndex c
                                                                     <*> paramCommandComment_getDirection c
                                          return $ Just v
    FFI.TParamCommandComment        -> do v <- TypeParamCommandComment c
                                                 <$> FFI.tParamCommandComment_getParamName c
                                                 <*> tParamCommandComment_getPosition c
                                          return $ Just v
    FFI.VerbatimBlockCommandComment -> Just . VerbatimBlockCommandComment c <$> blockCommandComment_getParagraph c
    FFI.VerbatimBlockLineComment    -> Just . VerbatimBlockLineComment c <$> FFI.verbatimBlockLineComment_getText c
    FFI.VerbatimLineComment         -> Just . VerbatimLineComment c <$> FFI.verbatimLineComment_getText c
    FFI.FullComment                 -> pure $ Just (FullComment c)

getFFIComment :: ParsedComment s ->  FFI.Comment s
getFFIComment (TextComment c _) = c
getFFIComment (InlineCommandComment c _ _ _) = c
getFFIComment (HTMLStartTagComment c _ _ _) = c
getFFIComment (HTMLEndTagComment c _) = c
getFFIComment (ParagraphComment c) = c
getFFIComment (BlockCommandComment c _ _ _) = c
getFFIComment (ParamCommandComment c _ _ _) = c
getFFIComment (TypeParamCommandComment c _ _) = c
getFFIComment (VerbatimBlockCommandComment c _) = c
getFFIComment (VerbatimBlockLineComment c _) = c
getFFIComment (VerbatimLineComment c _) = c
getFFIComment (FullComment c) = c

inlineCommandComment_getRenderKind :: ClangBase m => FFI.Comment s'
                                   -> ClangT s m FFI.InlineCommandRenderStyle
inlineCommandComment_getRenderKind c = liftIO $ FFI.inlineCommandComment_getRenderKind c

inlineCommandComment_getArgs :: ClangBase m => FFI.Comment s' -> ClangT s m [FFI.ClangString s]
inlineCommandComment_getArgs c = do
  numArgs <- liftIO $ FFI.inlineCommandComment_getNumArgs c
  mapM (FFI.inlineCommandComment_getArgText c) [0..(numArgs - 1)]

htmlStartTagComment_getAttrs :: ClangBase m => FFI.Comment s'
                             -> ClangT s m [(FFI.ClangString s, FFI.ClangString s)]
htmlStartTagComment_getAttrs c = do
  numAttrs <- liftIO $ FFI.hTMLStartTag_getNumAttrs c
  forM [0..numAttrs] $ \attr -> do
    attrName <- FFI.hTMLStartTag_getAttrName c attr
    attrValue <- FFI.hTMLStartTag_getAttrValue c attr
    return (attrName, attrValue)

htmlStartTagComment_isSelfClosing :: ClangBase m => FFI.Comment s' -> ClangT s m Bool
htmlStartTagComment_isSelfClosing c = liftIO $ FFI.hTMLStartTagComment_isSelfClosing c

blockCommandComment_getArgs :: ClangBase m => FFI.Comment s' -> ClangT s m [FFI.ClangString s]
blockCommandComment_getArgs c = do
  numArgs <- liftIO $ FFI.blockCommandComment_getNumArgs c
  mapM (FFI.blockCommandComment_getArgText c) [0..(numArgs - 1)]

blockCommandComment_getParagraph :: ClangBase m => FFI.Comment s' -> ClangT s m (Maybe (ParsedComment s))
blockCommandComment_getParagraph c =
  parseComment =<< liftIO (FFI.blockCommandComment_getParagraph mkProxy c)

paramCommandComment_getParamIndex :: ClangBase m => FFI.Comment s' -> ClangT s m (Maybe Int)
paramCommandComment_getParamIndex c = do
  valid <- liftIO $ FFI.paramCommandComment_isParamIndexValid c
  if valid
    then Just <$> (liftIO $ FFI.paramCommandComment_getParamIndex c)
    else return Nothing

paramCommandComment_getDirection :: ClangBase m => FFI.Comment s' -> ClangT s m ParamPassDirection
paramCommandComment_getDirection c = do
  dir <- liftIO $ FFI.paramCommandComment_getDirection c
  explicit <- liftIO $ FFI.paramCommandComment_isDirectionExplicit c
  return $ if explicit
             then ExplicitParamPassDirection dir
             else InferredParamPassDirection dir

tParamCommandComment_getPosition :: ClangBase m => FFI.Comment s' -> ClangT s m (Maybe [Int])
tParamCommandComment_getPosition c = do
  valid <- liftIO $ FFI.tParamCommandComment_isParamPositionValid c
  if valid
     then do depth <- liftIO $ FFI.tParamCommandComment_getDepth c
             indices <- forM [0..depth] $ \d ->
                          liftIO $ FFI.tParamCommandComment_getIndex c d
             return $ Just indices
     else return Nothing