{-# LANGUAGE
  MultiParamTypeClasses,
  ConstraintKinds,
  QuasiQuotes,
  UndecidableInstances,
  RankNTypes
  #-}
module LLVM.Internal.Attribute where

import LLVM.Prelude

import Control.Monad.AnyCont
import Control.Monad.IO.Class
import Control.Monad.State (gets)

import Foreign.C (CUInt)
import Foreign.Ptr
import Data.Either
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe

import qualified LLVM.Internal.FFI.Attribute as FFI
import qualified LLVM.Internal.FFI.LLVMCTypes as FFI
import LLVM.Internal.FFI.LLVMCTypes (parameterAttributeKindP, functionAttributeKindP)  

import qualified LLVM.AST.ParameterAttribute as A.PA  
import qualified LLVM.AST.FunctionAttribute as A.FA  

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

inconsistentCases :: Show a => String -> a -> b
inconsistentCases name attr =
  error $ "llvm-hs internal error: cases inconstistent in " ++ name ++ " encoding for " ++ show attr

instance Monad m => EncodeM m A.PA.ParameterAttribute (Ptr FFI.ParameterAttrBuilder -> EncodeAST ()) where
  encodeM a = return $ \b -> liftIO $ case a of
    A.PA.Alignment v -> FFI.attrBuilderAddAlignment b v
    A.PA.Dereferenceable v -> FFI.attrBuilderAddDereferenceable b v
    A.PA.DereferenceableOrNull v -> FFI.attrBuilderAddDereferenceableOrNull b v
    _ -> FFI.attrBuilderAddParameterAttributeKind b $ case a of
      A.PA.ZeroExt -> FFI.parameterAttributeKindZExt
      A.PA.SignExt -> FFI.parameterAttributeKindSExt
      A.PA.InReg -> FFI.parameterAttributeKindInReg
      A.PA.SRet -> FFI.parameterAttributeKindStructRet
      A.PA.NoAlias -> FFI.parameterAttributeKindNoAlias
      A.PA.ByVal -> FFI.parameterAttributeKindByVal
      A.PA.NoCapture -> FFI.parameterAttributeKindNoCapture
      A.PA.Nest -> FFI.parameterAttributeKindNest
      A.PA.ReadOnly -> FFI.parameterAttributeKindReadOnly
      A.PA.ReadNone -> FFI.parameterAttributeKindReadNone
      A.PA.InAlloca -> FFI.parameterAttributeKindInAlloca
      A.PA.NonNull -> FFI.parameterAttributeKindNonNull
      A.PA.Returned -> FFI.parameterAttributeKindReturned
      A.PA.SwiftSelf -> FFI.parameterAttributeKindSwiftSelf
      A.PA.SwiftError -> FFI.parameterAttributeKindSwiftError
      A.PA.WriteOnly -> FFI.parameterAttributeKindWriteOnly
      A.PA.Alignment _ -> inconsistentCases "ParameterAttribute" a
      A.PA.Dereferenceable _ -> inconsistentCases "ParameterAttribute" a
      A.PA.DereferenceableOrNull _ -> inconsistentCases "ParameterAttribute" a

instance Monad m => EncodeM m A.FA.FunctionAttribute (Ptr FFI.FunctionAttrBuilder -> EncodeAST ()) where
  encodeM (A.FA.StringAttribute kind value) = return $ \b -> do
    (kindP, kindLen) <- encodeM kind
    (valueP, valueLen) <- encodeM value
    liftIO $ FFI.attrBuilderAddStringAttribute b kindP kindLen valueP valueLen
  encodeM a = return $ \b -> case a of
    A.FA.StackAlignment v -> liftIO $ FFI.attrBuilderAddStackAlignment b v
    A.FA.AllocSize x y -> do
      x' <- encodeM x
      y' <- encodeM y
      liftIO $ FFI.attrBuilderAddAllocSize b x' y'
    _ -> liftIO $ FFI.attrBuilderAddFunctionAttributeKind b $ case a of
      A.FA.Convergent -> FFI.functionAttributeKindConvergent
      A.FA.InaccessibleMemOnly -> FFI.functionAttributeKindInaccessibleMemOnly
      A.FA.InaccessibleMemOrArgMemOnly -> FFI.functionAttributeKindInaccessibleMemOrArgMemOnly
      A.FA.NoReturn -> FFI.functionAttributeKindNoReturn
      A.FA.NoUnwind -> FFI.functionAttributeKindNoUnwind
      A.FA.ReadNone -> FFI.functionAttributeKindReadNone
      A.FA.ReadOnly -> FFI.functionAttributeKindReadOnly
      A.FA.NoInline -> FFI.functionAttributeKindNoInline
      A.FA.NoRecurse -> FFI.functionAttributeKindNoRecurse
      A.FA.AlwaysInline -> FFI.functionAttributeKindAlwaysInline
      A.FA.MinimizeSize -> FFI.functionAttributeKindMinSize
      A.FA.OptimizeForSize -> FFI.functionAttributeKindOptimizeForSize
      A.FA.OptimizeNone -> FFI.functionAttributeKindOptimizeNone
      A.FA.WriteOnly -> FFI.functionAttributeKindWriteOnly
      A.FA.ArgMemOnly -> FFI.functionAttributeKindArgMemOnly
      A.FA.StackProtect -> FFI.functionAttributeKindStackProtect
      A.FA.StackProtectReq -> FFI.functionAttributeKindStackProtectReq
      A.FA.StackProtectStrong -> FFI.functionAttributeKindStackProtectStrong
      A.FA.NoRedZone -> FFI.functionAttributeKindNoRedZone
      A.FA.NoImplicitFloat -> FFI.functionAttributeKindNoImplicitFloat
      A.FA.Naked -> FFI.functionAttributeKindNaked
      A.FA.InlineHint -> FFI.functionAttributeKindInlineHint
      A.FA.ReturnsTwice -> FFI.functionAttributeKindReturnsTwice
      A.FA.UWTable -> FFI.functionAttributeKindUWTable
      A.FA.NonLazyBind -> FFI.functionAttributeKindNonLazyBind
      A.FA.Builtin -> FFI.functionAttributeKindBuiltin
      A.FA.NoBuiltin -> FFI.functionAttributeKindNoBuiltin
      A.FA.Cold -> FFI.functionAttributeKindCold
      A.FA.JumpTable -> FFI.functionAttributeKindJumpTable
      A.FA.NoDuplicate -> FFI.functionAttributeKindNoDuplicate
      A.FA.SanitizeAddress -> FFI.functionAttributeKindSanitizeAddress
      A.FA.SanitizeThread -> FFI.functionAttributeKindSanitizeThread
      A.FA.SanitizeMemory -> FFI.functionAttributeKindSanitizeMemory
      A.FA.SafeStack -> FFI.functionAttributeKindSafeStack
      A.FA.StackAlignment _ -> inconsistentCases "FunctionAttribute" a
      A.FA.AllocSize _ _ -> inconsistentCases "FunctionAttribute" a
      A.FA.StringAttribute _ _ -> inconsistentCases "FunctionAttribute" a

instance DecodeM DecodeAST A.PA.ParameterAttribute FFI.ParameterAttribute where
  decodeM a = do
    enum <- liftIO $ FFI.parameterAttributeKindAsEnum a
    case enum of
      [parameterAttributeKindP|ZExt|] -> return A.PA.ZeroExt
      [parameterAttributeKindP|SExt|] -> return A.PA.SignExt
      [parameterAttributeKindP|InReg|] -> return A.PA.InReg
      [parameterAttributeKindP|StructRet|] -> return A.PA.SRet
      [parameterAttributeKindP|Alignment|] -> return A.PA.Alignment `ap` (liftIO $ FFI.attributeValueAsInt a)
      [parameterAttributeKindP|NoAlias|] -> return A.PA.NoAlias
      [parameterAttributeKindP|ByVal|] -> return A.PA.ByVal
      [parameterAttributeKindP|NoCapture|] -> return A.PA.NoCapture
      [parameterAttributeKindP|Nest|] -> return A.PA.Nest
      [parameterAttributeKindP|ReadOnly|] -> return A.PA.ReadOnly
      [parameterAttributeKindP|ReadNone|] -> return A.PA.ReadNone
      [parameterAttributeKindP|WriteOnly|] -> return A.PA.WriteOnly
      [parameterAttributeKindP|InAlloca|] -> return A.PA.InAlloca
      [parameterAttributeKindP|NonNull|] -> return A.PA.NonNull
      [parameterAttributeKindP|Dereferenceable|] -> return A.PA.Dereferenceable `ap` (liftIO $ FFI.attributeValueAsInt a)
      [parameterAttributeKindP|DereferenceableOrNull|] -> return A.PA.DereferenceableOrNull `ap` (liftIO $ FFI.attributeValueAsInt a)
      [parameterAttributeKindP|Returned|] -> return A.PA.Returned
      [parameterAttributeKindP|SwiftSelf|] -> return A.PA.SwiftSelf
      [parameterAttributeKindP|SwiftError|] -> return A.PA.SwiftError
      _ -> error $ "unhandled parameter attribute enum value: " ++ show enum

instance DecodeM DecodeAST A.FA.FunctionAttribute FFI.FunctionAttribute where
  decodeM a = do
    isString <- decodeM =<< (liftIO $ FFI.isStringAttribute a)
    if isString
       then
         return A.FA.StringAttribute
                  `ap` (decodeM $ FFI.attributeKindAsString a)
                  `ap` (decodeM $ FFI.attributeValueAsString a)                   
       else do
         enum <- liftIO $ FFI.functionAttributeKindAsEnum a
         case enum of
           [functionAttributeKindP|AllocSize|] -> do
             x <- alloca
             y <- alloca
             isJust <- liftIO $ FFI.attributeGetAllocSizeArgs a x y
             x' <- decodeM =<< peek x
             y' <- peek y
             yM <- decodeM (y', isJust)
             return (A.FA.AllocSize x' yM)
           [functionAttributeKindP|NoReturn|] -> return A.FA.NoReturn
           [functionAttributeKindP|NoUnwind|] -> return A.FA.NoUnwind
           [functionAttributeKindP|ReadNone|] -> return A.FA.ReadNone
           [functionAttributeKindP|ReadOnly|] -> return A.FA.ReadOnly
           [functionAttributeKindP|NoInline|] -> return A.FA.NoInline
           [functionAttributeKindP|NoRecurse|] -> return A.FA.NoRecurse
           [functionAttributeKindP|AlwaysInline|] -> return A.FA.AlwaysInline
           [functionAttributeKindP|MinSize|] -> return A.FA.MinimizeSize
           [functionAttributeKindP|OptimizeForSize|] -> return A.FA.OptimizeForSize
           [functionAttributeKindP|OptimizeNone|] -> return A.FA.OptimizeNone
           [functionAttributeKindP|StackProtect|] -> return A.FA.StackProtect
           [functionAttributeKindP|StackProtectReq|] -> return A.FA.StackProtectReq
           [functionAttributeKindP|StackProtectStrong|] -> return A.FA.StackProtectStrong
           [functionAttributeKindP|NoRedZone|] -> return A.FA.NoRedZone
           [functionAttributeKindP|NoImplicitFloat|] -> return A.FA.NoImplicitFloat
           [functionAttributeKindP|Naked|] -> return A.FA.Naked
           [functionAttributeKindP|InlineHint|] -> return A.FA.InlineHint
           [functionAttributeKindP|StackAlignment|] -> return A.FA.StackAlignment `ap` (liftIO $ FFI.attributeValueAsInt a)
           [functionAttributeKindP|ReturnsTwice|] -> return A.FA.ReturnsTwice
           [functionAttributeKindP|UWTable|] -> return A.FA.UWTable
           [functionAttributeKindP|NonLazyBind|] -> return A.FA.NonLazyBind
           [functionAttributeKindP|Builtin|] -> return A.FA.Builtin
           [functionAttributeKindP|NoBuiltin|] -> return A.FA.NoBuiltin
           [functionAttributeKindP|Cold|] -> return A.FA.Cold
           [functionAttributeKindP|JumpTable|] -> return A.FA.JumpTable
           [functionAttributeKindP|NoDuplicate|] -> return A.FA.NoDuplicate
           [functionAttributeKindP|SanitizeAddress|] -> return A.FA.SanitizeAddress
           [functionAttributeKindP|SanitizeThread|] -> return A.FA.SanitizeThread
           [functionAttributeKindP|SanitizeMemory|] -> return A.FA.SanitizeMemory
           [functionAttributeKindP|ArgMemOnly|] -> return A.FA.ArgMemOnly
           [functionAttributeKindP|Convergent|] -> return A.FA.Convergent
           [functionAttributeKindP|InaccessibleMemOnly|] -> return A.FA.InaccessibleMemOnly
           [functionAttributeKindP|InaccessibleMemOrArgMemOnly|] -> return A.FA.InaccessibleMemOrArgMemOnly
           [functionAttributeKindP|SafeStack|] -> return A.FA.SafeStack
           [functionAttributeKindP|WriteOnly|] -> return A.FA.WriteOnly
           _ -> error $ "unhandled function attribute enum value: " ++ show enum

allocaAttrBuilder :: (Monad m, MonadAnyCont IO m) => m (Ptr (FFI.AttrBuilder a))
allocaAttrBuilder = do
  p <- allocaArray FFI.getAttrBuilderSize
  anyContToM $ \f -> do
    ab <- FFI.constructAttrBuilder p
    r <- f ab
    FFI.destroyAttrBuilder ab
    return r

instance EncodeM EncodeAST a (Ptr (FFI.AttrBuilder b) -> EncodeAST ()) => EncodeM EncodeAST (FFI.Index, [a]) (FFI.AttributeSet b) where
  encodeM (index, as) = scopeAnyCont $ do
    ab <- allocaAttrBuilder
    builds <- mapM encodeM as
    void (forM builds ($ ab) :: EncodeAST [()])
    Context context <- gets encodeStateContext
    liftIO $ FFI.getAttributeSet context index ab

instance EncodeM EncodeAST [A.FA.FunctionAttribute] FFI.FunctionAttributeSet where
  encodeM fas = encodeM (FFI.functionIndex, fas)

instance DecodeM DecodeAST a (FFI.Attribute b) => DecodeM DecodeAST [a] (FFI.AttributeSet b) where
  decodeM as = do
    np <- alloca
    as <- liftIO $ FFI.attributeSetGetAttributes as 0 np
    n <- peek np
    decodeM (n, as)
            
data MixedAttributeSet = MixedAttributeSet {
    functionAttributes :: [Either A.FA.GroupID A.FA.FunctionAttribute],
    returnAttributes :: [A.PA.ParameterAttribute],
    parameterAttributes :: Map CUInt [A.PA.ParameterAttribute]
  }
  deriving (Eq, Show)

data PreSlot
  = IndirectFunctionAttributes A.FA.GroupID
  | DirectFunctionAttributes [A.FA.FunctionAttribute]
  | ReturnAttributes [A.PA.ParameterAttribute]
  | ParameterAttributes CUInt [A.PA.ParameterAttribute]    

instance EncodeM EncodeAST PreSlot FFI.MixedAttributeSet where
  encodeM preSlot = do
    let forget = liftM FFI.forgetAttributeType
    case preSlot of
      IndirectFunctionAttributes gid -> forget (referAttributeGroup gid)
      DirectFunctionAttributes fas -> forget (encodeM fas :: EncodeAST FFI.FunctionAttributeSet)
      ReturnAttributes as -> forget (encodeM (FFI.returnIndex, as) :: EncodeAST FFI.ParameterAttributeSet)
      ParameterAttributes i as -> forget (encodeM (fromIntegral (i + 1) :: FFI.Index, as) :: EncodeAST FFI.ParameterAttributeSet)

instance EncodeM EncodeAST MixedAttributeSet FFI.MixedAttributeSet where
  encodeM (MixedAttributeSet fAttrs rAttrs pAttrs) = do
    let directP = DirectFunctionAttributes (rights fAttrs)
        indirectPs = map IndirectFunctionAttributes (lefts fAttrs)
        returnP = ReturnAttributes rAttrs
        paramPs = [ ParameterAttributes x as | (x, as) <- Map.toList pAttrs ]
    (nAttrs, attrs) <- encodeM ([directP, returnP] ++ indirectPs ++ paramPs)
    Context context <- gets encodeStateContext
    liftIO $ FFI.mixAttributeSets context attrs nAttrs

instance DecodeM DecodeAST MixedAttributeSet FFI.MixedAttributeSet where
  decodeM mas = do
    numSlots <- if mas == nullPtr then return 0 else liftIO $ FFI.attributeSetNumSlots mas
    slotIndexes <- forM (take (fromIntegral numSlots) [0..]) $ \s -> do
      i <- liftIO $ FFI.attributeSetSlotIndex mas s
      return (i, s)
    let separate :: Ord k => k -> Map k a -> (Maybe a, Map k a)
        separate = Map.updateLookupWithKey (\_ _ -> Nothing)
        indexedSlots = Map.fromList slotIndexes
    unless (Map.size indexedSlots == length slotIndexes) $
           fail "unexpected slot index collision decoding mixed AttributeSet"
    let (functionSlot, otherSlots) = separate FFI.functionIndex (Map.fromList slotIndexes)
    functionAnnotation <- for (maybeToList functionSlot) $ \slot -> do
      a <- liftIO $ FFI.attributeSetSlotAttributes mas slot
      getAttributeGroupID a
    otherAttributeSets <- for otherSlots $ \slot -> do
      a <- liftIO $ FFI.attributeSetSlotAttributes mas slot
      decodeM (a :: FFI.ParameterAttributeSet)
    let (returnAttributeSet, shiftedParameterAttributeSets) = separate FFI.returnIndex otherAttributeSets
    return $ MixedAttributeSet {
                  functionAttributes = fmap Left functionAnnotation,
                  returnAttributes = join . maybeToList $ returnAttributeSet,
                  parameterAttributes = Map.mapKeysMonotonic (\x -> fromIntegral x - 1) shiftedParameterAttributeSets
                }