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

-- | Functions for manipulating 'FFI.SourceLocation's, which map a location in a
-- translation unit to a location in on-disk or in-memory files.
--
-- This module is intended to be imported qualified.
module Clang.Location
(

-- * Creating locations
  create
, createForOffset
, createInvalid

-- * Mapping locations to files
, getExpansionLocation
, getPresumedLocation
, getSpellingLocation
, getFileLocation

-- * Analyzing locations
, getCursor
, isInSystemHeader
, isFromMainFile

) where

import Control.Monad.IO.Class

import qualified Clang.Internal.FFI as FFI
import Clang.Internal.Monad
import System.IO.Unsafe (unsafePerformIO)

-- | Retrieves a 'FFI.SourceLocation' representing the given location in the source code.
create :: ClangBase m
       => FFI.TranslationUnit s'  -- ^ The translation unit.
       -> FFI.File s''            -- ^ The file.
       -> Int                     -- ^ The line.
       -> Int                     -- ^ The column.
       -> ClangT s m (FFI.SourceLocation s)  -- ^ A 'FFI.SourceLocation' value representing
                                                  -- the location.
create tu f line col = liftIO $ FFI.getLocation mkProxy tu f line col

-- | Like 'create', but uses an offset instead of a line and column.
createForOffset :: ClangBase m
                => FFI.TranslationUnit s'  -- ^ The translation unit.
                -> FFI.File s''            -- ^ The file.
                -> Int                     -- ^ The offset.
                -> ClangT s m (FFI.SourceLocation s)  -- ^ A 'FFI.SourceLocation' value
                                                           -- representing the location.
createForOffset tu f off = liftIO $ FFI.getLocationForOffset mkProxy tu f off

-- | Creates an invalid 'FFI.SourceLocation'.
createInvalid :: ClangBase m => ClangT s m (FFI.SourceLocation s)
createInvalid = liftIO $ FFI.getNullLocation mkProxy

-- | Retrieves the 'FFI.File', line, column, and offset associated with the given location.
--
-- If the location points into a macro expansion, retrieves the location of the macro expansion.
-- This may be a position that doesn't exist in the original source.
getExpansionLocation :: ClangBase m => FFI.SourceLocation s'
                     -> ClangT s m (Maybe (FFI.File s), Int, Int, Int)
getExpansionLocation l = liftIO $ FFI.getExpansionLocation mkProxy l

-- | Retrieves the 'FFI.File', line, and column associated with the given location, as
-- interpreted by the C preprocessor.
--
-- This may differ from the results given by 'getExpansionLocation' because it takes '#line'
-- directives into account, which 'getExpansionLocation' ignores.
getPresumedLocation :: ClangBase m => FFI.SourceLocation s'
                    -> ClangT s m (FFI.ClangString s, Int, Int)
getPresumedLocation = FFI.getPresumedLocation

-- | Retrieves the 'FFI.File', line, column, and offset associated with the given location.
--
-- If the location points into a macro expansion, returns the corresponding position in the
-- original source. This may be where the macro was defined or where it was instantiated,
-- depending on what exactly the location points to.
getSpellingLocation :: ClangBase m => FFI.SourceLocation s'
                    -> ClangT s m (Maybe (FFI.File s), Int, Int, Int)
getSpellingLocation l = liftIO $ FFI.getSpellingLocation mkProxy l

-- | Retrieves the 'FFI.File', line, column, and offset associated with the given location.
--
-- If the location points into a macro expansion, returns the position where the macro was
-- expanded or the position of the macro argument, if the cursor points at a macro argument.
getFileLocation :: ClangBase m => FFI.SourceLocation s'
                    -> ClangT s m (Maybe (FFI.File s), Int, Int, Int)
getFileLocation l = liftIO $ FFI.getFileLocation mkProxy l

-- | Retrieves an AST cursor at the given source location.
--
-- The cursor can be traversed using the functions in "Clang.Cursor".
getCursor :: ClangBase m => FFI.TranslationUnit s' -> FFI.SourceLocation s''
          -> ClangT s m (FFI.Cursor s)
getCursor tu sl = liftIO $ FFI.getCursor mkProxy tu sl

-- | Returns 'True' if the given location is inside a system header.
isInSystemHeader :: FFI.SourceLocation s' -> Bool
isInSystemHeader l = unsafePerformIO $ FFI.location_isInSystemHeader l

-- | Returns 'True' if the given location is from the main file of the associated translation
-- unit.
isFromMainFile :: FFI.SourceLocation s' -> Bool
isFromMainFile l = unsafePerformIO $ FFI.location_isFromMainFile l