{-# LANGUAGE
  TemplateHaskell,
  MultiParamTypeClasses,
  OverloadedStrings
  #-}
module LLVM.Internal.Global where

import LLVM.Prelude

import Control.Monad.State
import Control.Monad.AnyCont
import Foreign.Ptr
import qualified Data.Map as Map

import qualified LLVM.Internal.FFI.PtrHierarchy as FFI
import qualified LLVM.Internal.FFI.LLVMCTypes as FFI
import qualified LLVM.Internal.FFI.GlobalValue as FFI

import LLVM.Internal.Coding
import LLVM.Internal.DecodeAST
import LLVM.Internal.EncodeAST

import qualified LLVM.AST.Linkage as A.L
import qualified LLVM.AST.Visibility as A.V
import qualified LLVM.AST.COMDAT as A.COMDAT
import qualified LLVM.AST.DLL as A.DLL
import qualified LLVM.AST.ThreadLocalStorage as A.TLS
import qualified LLVM.AST.Global as A.G

genCodingInstance [t| A.L.Linkage |] ''FFI.Linkage [
  (FFI.linkageExternal, A.L.External),
  (FFI.linkageAvailableExternally, A.L.AvailableExternally),
  (FFI.linkageLinkOnceAny, A.L.LinkOnce),
  (FFI.linkageLinkOnceODR, A.L.LinkOnceODR),
  (FFI.linkageWeakAny, A.L.Weak),
  (FFI.linkageWeakODR, A.L.WeakODR),
  (FFI.linkageAppending, A.L.Appending),
  (FFI.linkageInternal, A.L.Internal),
  (FFI.linkagePrivate, A.L.Private),
  (FFI.linkageExternalWeak, A.L.ExternWeak),
  (FFI.linkageCommon, A.L.Common)
 ]

getLinkage :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST A.L.Linkage
getLinkage g = liftIO $ decodeM =<< FFI.getLinkage (FFI.upCast g)

setLinkage :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> A.L.Linkage -> EncodeAST ()
setLinkage g l = liftIO . FFI.setLinkage (FFI.upCast g) =<< encodeM l

genCodingInstance [t| A.V.Visibility |] ''FFI.Visibility [
  (FFI.visibilityDefault, A.V.Default),
  (FFI.visibilityHidden, A.V.Hidden),
  (FFI.visibilityProtected, A.V.Protected)
 ]

getVisibility :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST A.V.Visibility
getVisibility g = liftIO $ decodeM =<< FFI.getVisibility (FFI.upCast g)

setVisibility :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> A.V.Visibility -> EncodeAST ()
setVisibility g v = liftIO . FFI.setVisibility (FFI.upCast g) =<< encodeM v

genCodingInstance [t| Maybe A.DLL.StorageClass |] ''FFI.DLLStorageClass [
  (FFI.dllStorageClassDefault, Nothing),
  (FFI.dllStorageClassDLLImport, Just A.DLL.Import),
  (FFI.dllStorageClassDLLExport, Just A.DLL.Export)
 ]

getDLLStorageClass :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST (Maybe A.DLL.StorageClass)
getDLLStorageClass g = liftIO $ decodeM =<< FFI.getDLLStorageClass (FFI.upCast g)

setDLLStorageClass :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> Maybe A.DLL.StorageClass -> EncodeAST ()
setDLLStorageClass g sc = liftIO . FFI.setDLLStorageClass (FFI.upCast g) =<< encodeM sc

getSection :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST (Maybe ShortByteString)
getSection g = do
  sectionLengthPtr <- alloca
  sectionNamePtr <- liftIO $ FFI.getSection (FFI.upCast g) sectionLengthPtr
  if sectionNamePtr == nullPtr then
    return Nothing
    else
      do sectionLength <- peek sectionLengthPtr
         sectionName <- decodeM (sectionNamePtr, sectionLength)
         return (Just sectionName)

setSection :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> Maybe ShortByteString -> EncodeAST ()
setSection g s = scopeAnyCont $ do
  s <- encodeM (fromMaybe "" s)
  liftIO $ FFI.setSection (FFI.upCast g) s

genCodingInstance [t| A.COMDAT.SelectionKind |] ''FFI.COMDATSelectionKind [
  (FFI.comdatSelectionKindAny, A.COMDAT.Any),
  (FFI.comdatSelectionKindExactMatch, A.COMDAT.ExactMatch),
  (FFI.comdatSelectionKindLargest, A.COMDAT.Largest),
  (FFI.comdatSelectionKindNoDuplicates, A.COMDAT.NoDuplicates),
  (FFI.comdatSelectionKindSameSize, A.COMDAT.SameSize)
 ]

instance DecodeM DecodeAST (ShortByteString, A.COMDAT.SelectionKind) (Ptr FFI.COMDAT) where
  decodeM c =
    (,)
      <$> decodeM (FFI.getCOMDATName c)
      <*> (decodeM =<< liftIO (FFI.getCOMDATSelectionKind c))

getCOMDATName :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST (Maybe ShortByteString)
getCOMDATName g = do
  c <- liftIO $ FFI.getCOMDAT (FFI.upCast g)
  if c == nullPtr
   then return Nothing
   else do
     cds <- gets comdats
     liftM Just $ case Map.lookup c cds of
       Just (name, _) -> return name
       Nothing -> do
          cd@(name, _) <- decodeM c
          modify $ \s -> s { comdats = Map.insert c cd cds }
          return name

setCOMDAT :: FFI.DescendentOf FFI.GlobalObject v => Ptr v -> Maybe ShortByteString -> EncodeAST ()
setCOMDAT _ Nothing = return ()
setCOMDAT g (Just name) = do
  cd <- referCOMDAT name
  liftIO $ FFI.setCOMDAT (FFI.upCast g) cd

setAlignment :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> Word32 -> EncodeAST ()
setAlignment g i = liftIO $ FFI.setAlignment (FFI.upCast g) (fromIntegral i)

getAlignment :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST Word32
getAlignment g = liftIO $ fromIntegral <$> FFI.getAlignment (FFI.upCast g)

genCodingInstance [t| Maybe A.TLS.Model |] ''FFI.ThreadLocalMode [
  (FFI.threadLocalModeNotThreadLocal, Nothing),
  (FFI.threadLocalModeGeneralDynamicTLSModel, Just A.TLS.GeneralDynamic),
  (FFI.threadLocalModeLocalDynamicTLSModel, Just A.TLS.LocalDynamic),
  (FFI.threadLocalModeInitialExecTLSModel, Just A.TLS.InitialExec),
  (FFI.threadLocalModeLocalExecTLSModel, Just A.TLS.LocalExec)
 ]

getThreadLocalMode :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> DecodeAST (Maybe A.TLS.Model)
getThreadLocalMode g = liftIO $ decodeM =<< FFI.getThreadLocalMode (FFI.upCast g)

setThreadLocalMode :: FFI.DescendentOf FFI.GlobalValue v => Ptr v -> Maybe A.TLS.Model -> EncodeAST ()
setThreadLocalMode g m = liftIO . FFI.setThreadLocalMode (FFI.upCast g) =<< encodeM m

genCodingInstance [t| Maybe A.G.UnnamedAddr |] ''FFI.UnnamedAddr [
  (FFI.unnamedAddrNone, Nothing),
  (FFI.unnamedAddrLocal, Just A.G.LocalAddr),
  (FFI.unnamedAddrGlobal, Just A.G.GlobalAddr)
 ]