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

-- | Functions for performing code completion.
--
-- To get started with code completion, it's enough to parse a file with
-- 'Clang.parseSourceFile' and pass the 'FFI.TranslationUnit' to 'codeCompleteAt'.
-- This will return a 'FFI.CodeCompleteResults' value, from which you can
-- retrieve a list of completion strings using 'getResults'. Each completion
-- string in turn consists of a series of chunks, which you can retrieve using
-- 'getChunks'.
--
-- This module is intended to be imported qualified.
module Clang.Completion
(
-- * Code completion
  codeCompleteAt
, FFI.CodeCompleteFlags(..)
, FFI.CodeCompleteResults

-- * Completion results
, getResults
, FFI.CompletionString
, sortResults
, getDiagnostics
, getContexts
, FFI.CompletionContext(..)
, getContainerKind
, getContainerUSR
, getObjCSelector

-- * Completion strings
, getChunks
, Chunk(..)
, FFI.ChunkKind(..)
, getPriority
, getAvailability
, getAnnotations
, getParent
, getBriefComment
) where

import Control.Monad
import Control.Monad.IO.Class
import Data.Typeable
import qualified Data.Vector as DV

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

-- | Perform code completion at a given location in a translation unit.
--
-- This function performs code completion at a particular file, line, and
-- column within source code, providing results that suggest potential
-- code snippets based on the context of the completion. The basic model
-- for code completion is that Clang will parse a complete source file,
-- performing syntax checking up to the location where code completion has
-- been requested. At that point, a special code completion token is passed
-- to the parser, which recognizes this token and determines, based on the
-- current location in the C \/ Objective-C \/ C++ grammar and the state of
-- semantic analysis, what completions to provide. These completions are
-- returned via a 'FFI.CodeCompleteResults' value.
--
-- Code completion itself is meant to be triggered by the client when the
-- user types punctuation characters or whitespace, at which point the
-- code completion location will coincide with the cursor. For example, if \'p\'
-- is a pointer, code completion might be triggered after the \'-\' and then
-- after the \'>\' in \'p->\'. When the code completion location is after the \'>\',
-- the completion results will provide, e.g., the members of the struct that
-- \'p\' points to. The client is responsible for placing the cursor at the
-- beginning of the token currently being typed, then filtering the results
-- based on the contents of the token. For example, when code-completing for
-- the expression \'p->get\', the client should provide the location just after
-- the \'>\' (e.g., pointing at the \'g\') to this code completion hook. Then, the
-- client can filter the results based on the current token text (\'get\'), only
-- showing those results that start with \'get\'. The intent of this interface
-- is to separate the relatively high-latency acquisition of code completion
-- results from the filtering of results on a per-character basis, which must
-- have a lower latency.
codeCompleteAt ::
      ClangBase m
   => FFI.TranslationUnit s'  -- ^ The translation unit in which code completion should occur.
                              -- The source files for this translation unit need not be
                              -- completely up-to-date (and the contents of those source files
                              -- may be overridden via the 'FFI.UnsavedFile' vector). Cursors
                              -- referring into the translation unit may be invalidated by
                              -- this invocation.
  -> FilePath  -- ^ The name of the source file where code
               -- completion should be performed. This filename may be any file
               -- included in the translation unit.
  -> Int  -- ^ The line at which code completion should occur.
  -> Int  -- ^ The column at which code completion should occur.
          -- Note that the column should point just after the syntactic construct that
          -- initiated code completion, and not in the middle of a lexical token.
  -> DV.Vector FFI.UnsavedFile  -- ^ Files that have not yet been saved to disk
                                -- but may be required for parsing or code completion, including
                                -- the contents of those files.  The contents and name of these
                                -- files (as specified by 'FFI.UnsavedFile') are copied when
                                -- necessary, so the client only needs to guarantee their
                                -- validity until the call to this function returns.
  -> Maybe [FFI.CodeCompleteFlags]  -- ^ Extra options that control the behavior of code
                                    -- completion, or 'Nothing' to use the default set.
  -> ClangT s m (FFI.CodeCompleteResults s)
codeCompleteAt t fname l c ufs mayOpts = do
  opts <- case mayOpts of
    Just os -> return os
    Nothing -> unFlags <$> liftIO FFI.defaultCodeCompleteOptions
  FFI.codeCompleteAt t fname l c ufs (orFlags opts)

-- | Retrieves a list of code completion results.
--
-- The first element of each tuple is the completion string, which describes how to insert
-- this result into the editing buffer. Use 'getChunks' to analyze it further.
--
-- The second element of each tuple is the kind of entity that this completion refers to.
-- It will be a macro, keyword, or declaration describing the entity that the completion
-- is referring to.
getResults :: ClangBase m => FFI.CodeCompleteResults s'
           -> ClangT s m [(FFI.CompletionString s, FFI.CursorKind)]
getResults rs = do
  numS <- liftIO $ FFI.codeCompleteGetNumResults rs
  forM [0..(numS - 1)] $ \i ->
    FFI.codeCompleteGetResult rs i

-- | Sort the provided code completion results in case-insensitive alphabetical order.
sortResults :: ClangBase m => FFI.CodeCompleteResults s' -> ClangT s m ()
sortResults c = liftIO $ FFI.sortCodeCompletionResults c

-- | Retrieves the diagnostics associated with the given code completion.
getDiagnostics :: ClangBase m => FFI.CodeCompleteResults s' -> ClangT s m [FFI.Diagnostic s]
getDiagnostics c = do
  numD <- liftIO $ FFI.codeCompleteGetNumDiagnostics c
  mapM (FFI.codeCompleteGetDiagnostic c) [0..(numD - 1)]

-- | Determines which kinds of completions are appropriate for the context
-- of the given code completion.
getContexts :: ClangBase m => FFI.CodeCompleteResults s' -> ClangT s m [FFI.CompletionContext]
getContexts rs = unFlags <$> liftIO (FFI.codeCompleteGetContexts rs)

-- | Returns a cursor kind for the container associated with the given code
-- completion. Only contexts like member accesses and Objective-C message sends
-- have containers; for other kinds of contexts, a cursor kind of
-- 'FFI.InvalidCodeCursor' is returned.
--
-- The second element of the result tuple is a boolean indicating whether libclang
-- has incomplete information about the container. A 'True' value indicates that
-- information about the container is incomplete.
getContainerKind :: ClangBase m => FFI.CodeCompleteResults s'
                 -> ClangT s m (FFI.CursorKind, Bool)
getContainerKind rs = liftIO $ FFI.codeCompleteGetContainerKind rs

-- | Given a code completion context, returns a "Clang.USR" for the appropriate
-- container. Only contexts like member accesses and Objective-C message sends
-- have containers; for other kinds of contexts, the empty string is returned.
getContainerUSR :: ClangBase m => FFI.CodeCompleteResults s' -> ClangT s m (FFI.ClangString s)
getContainerUSR = FFI.codeCompleteGetContainerUSR

-- | Returns the currently-entered selector for an Objective-C message
-- send, formatted like \"initWithFoo:bar:\". This function is only guaranteed
-- to return a non-empty string if the completion context is an Objective-C
-- instance message or class message send, which you can check with 'getContexts'.
getObjCSelector :: ClangBase m => FFI.CodeCompleteResults s' -> ClangT s m (FFI.ClangString s)
getObjCSelector = FFI.codeCompleteGetObjCSelector

-- | The completion string is a template that describes not only the completion itself,
-- but also information about how it should be presented to the user. It is divided into
-- a list of chunks, with each kind of chunk playing a different role in the template.
data Chunk s
  = TextChunk FFI.ChunkKind (FFI.ClangString s)
  | OptionalChunk (FFI.CompletionString s)
    deriving (Eq, Ord, Typeable)

-- | Retrieves the chunks within a completion string.
--
-- Each \"chunk\" contains either a piece of text with a specific \"kind\"
-- that describes how that text should be interpreted by the client, or
-- another completion string.
getChunks :: ClangBase m => FFI.CompletionString s' -> ClangT s m [Chunk s]
getChunks cs = do
  numC <- liftIO $ FFI.getNumCompletionChunks cs
  forM [0..(numC - 1)] $ \i -> do
    kind <- liftIO $ FFI.getCompletionChunkKind cs i
    case kind of
      FFI.OptionalChunkKind ->
        OptionalChunk <$> (liftIO $ FFI.getCompletionChunkCompletionString cs i)
      _ ->
        TextChunk kind <$> FFI.getCompletionChunkText cs i

-- | Determines the priority of this code completion string.
--
-- The priority of a code completion indicates how likely it is that this
-- particular completion is the completion that the user will select. The
-- priority is selected by various internal heuristics. Smaller values
-- indicate more likely completions.
getPriority :: ClangBase m => FFI.CompletionString s' -> ClangT s m Int
getPriority cs = liftIO $ FFI.getCompletionPriority cs

-- | Determines the availability of the entity that this code completion
-- string refers to.
getAvailability :: ClangBase m => FFI.CompletionString s' -> ClangT s m FFI.AvailabilityKind
getAvailability cs = liftIO $ FFI.getCompletionAvailability cs

-- | Retrieves the annotations associated with the given completion string.
getAnnotations :: ClangBase m => FFI.CompletionString s' -> ClangT s m [FFI.ClangString s]
getAnnotations cs = do
  numA <- liftIO $ FFI.getCompletionNumAnnotations cs
  mapM (FFI.getCompletionAnnotation cs) [0..(numA - 1)]

-- | Retrieves the parent context of the given completion string.
--
-- The parent context of a completion string is the semantic parent of
-- the declaration (if any) that the code completion represents. For example,
-- a code completion for an Objective-C method would have the method's class
-- or protocol as its context. A completion string representing a method on
-- the NSObject class might have a parent context of \"NSObject\".
getParent :: ClangBase m => FFI.CompletionString s' -> ClangT s m (FFI.ClangString s)
getParent = FFI.getCompletionParent

-- | Retrieves the brief documentation comment attached to the declaration
-- that corresponds to the given completion string.
getBriefComment :: ClangBase m => FFI.CompletionString s' -> ClangT s m (FFI.ClangString s)
getBriefComment = FFI.getCompletionBriefComment