{- |
Module          : Foreign.C.Structs.Utils
Description     : Create C structs from Haskell
Copyright       : (c) Simon Plakolb, 2020
License         : MIT
Maintainer      : s.plakolb@gmail.com
Stability       : beta

This module defined some utility functions for Storable instance declarations.
-}
module Foreign.C.Structs.Utils (
      next, fmax, sizeof
) where

import Foreign.Storable (Storable, peek, sizeOf, alignment)
import Foreign.Marshal (alloca)
import Foreign.Ptr (Ptr, plusPtr, alignPtr)

-- | Due to alignment constraints the size of C structs is dependent on the order of fields and their respectible sizes. The function 'sizeof' can calculate the resulting size given a list of all 'alignments' and 'sizes'.
sizeof :: [Int] -> [Int] -> Int
sizeof alignments sizes = sizeof' 0 alignments sizes
    where
          sizeof' 0 (a:as) (s:ss) = sizeof' s as ss
          sizeof' s [] [] = s `pad` foldr max 0 alignments
          sizeof' x (a:as) (s:ss) = let
                            s' = x+s
                            in sizeof' (s' `pad` a) as ss

pad x a
  | x `mod` a == 0 = x
  | otherwise      = pad (x+1) a

-- | Jumps to the next pointer location in the struct.
next :: (Storable a, Storable b, Storable c) => Ptr a -> b -> IO (Ptr c)
next ptr x = alloca $ next' ptr x
    where next' :: (Storable a, Storable b, Storable c) => Ptr a -> b -> Ptr c -> IO (Ptr c)
          next' ptr x ptr_x = do
                let ptr_y = plusPtr ptr $ sizeOf x
                y <- peek ptr_x
                return $ alignPtr ptr_y $ alignment y

-- | Alias for @foldr max 0@.
fmax :: Integral a => [a] -> a
fmax = foldr max 0