{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}

-- | Functions for manipulating translation units.
--
-- To start analyzing a translation unit, call 'Index.withNew' to create a new index and
-- call 'withParsed' in the callback. Inside the callback for 'withParsed', you'll have
-- access to a 'FFI.TranslationUnit' value; you can call 'getCursor' to turn that into an
-- AST cursor which can be traversed using the functions in "Clang.Cursor".
--
-- This module is intended to be imported qualified.
module Clang.TranslationUnit
(

-- * Creating a translation unit
  withParsed
, withLoaded
, withReparsing
, FFI.TranslationUnitFlags
, FFI.ReparseFlags
, ReparsingCallback
, ParseContinuation(..)

-- * Saving
, save
, FFI.SaveTranslationUnitFlags

-- AST traversal and metadata
, getCursor
, getDiagnosticSet
, getSpelling

) where

import Control.Monad.IO.Class
import Data.Maybe (fromMaybe)
import qualified Data.Vector as DV
import System.FilePath ((</>))

import Clang.Internal.BitFlags
import Clang.Internal.Monad
import qualified Clang.Internal.FFI as FFI
import Paths_LibClang (getDataFileName)

-- | Creates a translation unit by parsing source code.
--
-- Note that not all command line arguments which are accepted by the \'clang\' frontend can
-- be used here. You should avoid passing \'-c\', \'-o\', \'-fpch-deps\', and the various
-- \'-M\' options. If you provide a 'FilePath' when calling 'withParsed', also be sure not
-- to provide the filename in the command line arguments as well.
withParsed :: ClangBase m
           => FFI.Index s'  -- ^ The index into which the translation unit should be loaded.
           -> Maybe FilePath  -- ^ The file to load, or 'Nothing' if the file is specified in
                              --   the command line arguments.
           -> [String]  -- ^ The command line arguments libclang should use when compiling this
                        --   file. Most arguments which you'd use with the \'clang\' frontend
                        --   are accepted.
           -> DV.Vector FFI.UnsavedFile  -- ^ Unsaved files which may be needed to parse this
                                         --   translation unit. This may include the source
                                         --   file itself or any file it includes.
           -> [FFI.TranslationUnitFlags]  -- ^ Flags that affect the processing of this
                                          --   translation unit.
           -> (forall s. FFI.TranslationUnit s -> ClangT s m a)  -- ^ A callback.
           -> ClangT s' m (Maybe a)  -- ^ The return value of the callback, or 'Nothing'
                                     --   if the file couldn't be parsed.
withParsed idx mayPath args ufs flags f = do
    liftIO $ FFI.setClangResourcesPath idx =<< clangResourcesPath
    mayTU <- FFI.parseTranslationUnit idx mayPath args ufs (orFlags flags)
    traverse go mayTU
  where
    go tu = clangScope $ f =<< fromOuterScope tu

-- | Creates a translation unit by loading a saved AST file.
--
-- Such an AST file can be created using 'save'.
withLoaded :: ClangBase m
           => FFI.Index s'  -- ^ The index into which the translation unit should be loaded.
           -> FilePath      -- ^ The file to load.
           -> (forall s. FFI.TranslationUnit s -> ClangT s m a)  -- ^ A callback.
           -> ClangT s' m a
withLoaded idx path f = do
  liftIO $ FFI.setClangResourcesPath idx =<< clangResourcesPath
  f =<< FFI.createTranslationUnit idx path


-- | Creates a translation unit by parsing source code.
--
-- This works like 'withParsed', except that the translation unit can be reparsed over and over
-- again by returning a 'Reparse' value from the callback. This is useful for interactive
-- analyses like code completion. Processing can be stopped by returning a 'ParseComplete' value.
withReparsing :: ClangBase m
              => FFI.Index s'  -- ^ The index into which the translation unit should be loaded.
              -> Maybe FilePath  -- ^ The file to load, or 'Nothing' if the file is specified in
                                 --   the command line arguments.
              -> [String]  -- ^ The command line arguments libclang should use when compiling
                           --   this file. Most arguments which you'd use with the \'clang\'
                           --   frontend are accepted.
              -> DV.Vector FFI.UnsavedFile  -- ^ Unsaved files which may be needed to parse this
                                            --   translation unit. This may include the source
                                            --   file itself or any file it includes.
              -> [FFI.TranslationUnitFlags]  -- ^ Flags that affect the processing of this
                                             --   translation unit.
              -> ReparsingCallback m r  -- ^ A callback which uses the translation unit. May be
                                        --   called many times depending on the return value.
                                        --   See 'ParseContinuation' for more information.
              -> ClangT s' m (Maybe r)  -- ^ The return value of the callback, as passed to
                                        --   'ParseComplete', or 'Nothing' if the file could
                                        --   not be parsed.
withReparsing idx mayPath args ufs flags f = do
    liftIO $ FFI.setClangResourcesPath idx =<< clangResourcesPath
    mayTU <- FFI.parseTranslationUnit idx mayPath args ufs (orFlags flags)
    case mayTU of
      Nothing -> return Nothing
      Just tu -> iterReparse f tu

iterReparse :: ClangBase m
            => ReparsingCallback m r
            -> FFI.TranslationUnit s'
            -> ClangT s' m (Maybe r)
iterReparse f tu = do
  cont <- clangScope $ f =<< fromOuterScope tu
  case cont of
    Reparse nextF ufs mayFlags ->
      do res <- FFI.reparseTranslationUnit tu ufs (makeFlags mayFlags)
         if res then iterReparse nextF tu
                else return Nothing
    ParseComplete finalVal -> return $ Just finalVal
  where
    makeFlags = orFlags . (fromMaybe [FFI.DefaultReparseFlags])


-- | A callback for use with 'withReparsing'.
type ReparsingCallback m r = forall s. FFI.TranslationUnit s
                          -> ClangT s m (ParseContinuation m r)

-- | A continuation returned by a 'ReparsingCallback'.
data ParseContinuation m r
  -- | 'Reparse' signals that the translation unit should be reparsed. It contains a callback
  -- which will be called with the updated translation unit after reparsing, a 'DV.Vector' of
  -- unsaved files which may be needed to reparse, and a set of flags affecting reparsing. The
  -- default reparsing flags can be requested by specifying 'Nothing'.
  = Reparse (ReparsingCallback m r) (DV.Vector FFI.UnsavedFile) (Maybe [FFI.ReparseFlags])

  -- | 'ParseComplete' signals that processing is finished. It contains a final result which
  -- will be returned by 'withReparsing'.
  | ParseComplete r

clangResourcesPath :: IO FilePath
clangResourcesPath =
  getDataFileName $ "build" </> "out" </> "lib" </> "clang" </> "3.4"

-- | Saves a translation unit as an AST file with can be loaded later using 'withLoaded'.
save :: ClangBase m
     => FFI.TranslationUnit s'  -- ^ The translation unit to save.
     -> FilePath                -- ^ The filename to save to.
     -> Maybe [FFI.SaveTranslationUnitFlags]  -- ^ Flags that affect saving, or 'Nothing' for
                                              --   the default set of flags.
     -> ClangT s m Bool
save t fname mayFlags = liftIO $ FFI.saveTranslationUnit t fname (orFlags flags)
  where flags = fromMaybe [FFI.DefaultSaveTranslationUnitFlags] mayFlags

{-
-- | Reparses the provided translation unit using the same command line arguments
-- that were originally used to parse it. If the file has changed on disk, or if
-- the unsaved files have changed, those changes will become visible.
--
-- Note that 'reparse' invalidates all cursors and source locations that refer into
-- the reparsed translation unit. This makes it unsafe. However, 'reparse' can be
-- more efficient than calling 'withParsed' a second time.
reparse :: ClangBase m
        => FFI.TranslationUnit s'    -- ^ The translation unit to reparse.
        -> DV.Vector FFI.UnsavedFile -- ^ Unsaved files which may be needed to reparse
                                     --   this translation unit.
        -> Maybe [FFI.ReparseFlags]  -- ^ Flags that affect reparsing, or 'Nothing' for the
                                     --   default set of flags.
        -> ClangT s m Bool
reparse t ufs mayFlags = FFI.reparseTranslationUnit t ufs (orFlags flags)
  where flags = fromMaybe [FFI.DefaultReparseFlags] mayFlags
-}

-- | Retrieve the cursor associated with this translation unit. This cursor is the root of
-- this translation unit's AST; you can begin exploring the AST further using the functions
-- in "Clang.Cursor".
getCursor :: ClangBase m => FFI.TranslationUnit s' -> ClangT s m (FFI.Cursor s)
getCursor tu = liftIO $ FFI.getTranslationUnitCursor mkProxy tu

-- | Retrieve the complete set of diagnostics associated with the given translation unit.
getDiagnosticSet :: ClangBase m => FFI.TranslationUnit s'-> ClangT s m (FFI.DiagnosticSet s)
getDiagnosticSet = FFI.getDiagnosticSetFromTU

-- | Retrieve a textual representation of this translation unit.
getSpelling :: ClangBase m => FFI.TranslationUnit s' -> ClangT s m (FFI.ClangString s)
getSpelling = FFI.getTranslationUnitSpelling