module LLVM.Extra.Array (
   size,
   assemble,
   extractAll,
   map,
   ) where

import qualified LLVM.Extra.Class as Class

import qualified LLVM.Core as LLVM
import LLVM.Core (Value, Array, CodeGenFunction, )

import qualified Type.Data.Num.Decimal as TypeNum
import Control.Monad.HT ((<=<), )
import Control.Monad (foldM, )

import qualified Data.List as List

import Data.Word (Word32, )

import Prelude hiding
          (Real, truncate, floor, round,
           map, zipWith, iterate, replicate, reverse, concat, sum, )


-- * target independent functions

size ::
   (TypeNum.Natural n) =>
   Value (Array n a) -> Int
size =
   let sz :: (TypeNum.Natural n) => TypeNum.Singleton n -> Value (Array n a) -> Int
       sz n _ = TypeNum.integralFromSingleton n
   in  sz TypeNum.singleton

{- |
construct an array out of single elements

You must assert that the length of the list matches the array size.

This can be considered the inverse of 'extractAll'.
-}
assemble ::
   (TypeNum.Natural n, LLVM.IsFirstClass a, LLVM.IsSized a) =>
   [Value a] -> CodeGenFunction r (Value (Array n a))
assemble =
   foldM (\v (k,x) -> LLVM.insertvalue v x (k::Word32)) Class.undefTuple .
   List.zip [0..]

{- |
provide the elements of an array as a list of individual virtual registers

This can be considered the inverse of 'assemble'.
-}
extractAll ::
   (TypeNum.Natural n, LLVM.IsFirstClass a, LLVM.IsSized a) =>
   Value (Array n a) -> LLVM.CodeGenFunction r [Value a]
extractAll x =
   mapM
      (LLVM.extractvalue x)
      (take (size x) [(0::Word32)..])

{- |
The loop is unrolled,
since 'LLVM.insertvalue' and 'LLVM.extractvalue' expect constant indices.
-}
map ::
   (TypeNum.Natural n,
    LLVM.IsFirstClass a, LLVM.IsSized a,
    LLVM.IsFirstClass b, LLVM.IsSized b) =>
   (Value a -> CodeGenFunction r (Value b)) ->
   (Value (Array n a) -> CodeGenFunction r (Value (Array n b)))
map f =
   assemble <=< mapM f <=< extractAll