{-# LANGUAGE CPP                   #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE ScopedTypeVariables   #-}

-- | This module gives ways to force the alignment of types.
module Raaz.Core.Types.Aligned
  ( -- * Types to force alignment.
    Aligned, unAligned, aligned16Bytes, aligned32Bytes, aligned64Bytes
  ) where


#if MIN_VERSION_base(4,7,0)
import           Data.Proxy
#endif

import           GHC.TypeLits
import           Foreign.Ptr                 ( castPtr      )
import           Foreign.Storable            ( Storable(..) )
import           Prelude hiding              ( length       )


-- | A type @w@ forced to be aligned to the alignment boundary @alg@
newtype Aligned (align :: Nat) w
  = Aligned { unAligned :: w -- ^ The underlying unAligned value.
            }

-- | Align the value to 16-byte boundary
aligned16Bytes :: w -> Aligned 16 w
{-# INLINE aligned16Bytes #-}

-- | Align the value to 32-byte boundary
aligned32Bytes :: w -> Aligned 32 w
{-# INLINE aligned32Bytes #-}

-- | Align the value to 64-byte boundary
aligned64Bytes :: w -> Aligned 64 w
{-# INLINE aligned64Bytes #-}

aligned16Bytes = Aligned
aligned32Bytes = Aligned
aligned64Bytes = Aligned

#if MIN_VERSION_base(4,7,0)

-- | The constraint on the alignment o(since base 4.7.0).
type AlignBoundary (alg :: Nat) = KnownNat alg

alignmentBoundary :: AlignBoundary alg => Aligned alg a -> Int
alignmentBoundary = aB Proxy
  where aB :: AlignBoundary algn => Proxy algn -> Aligned algn a -> Int
        aB algn _ = fromEnum $ natVal algn

#else

-- | The constraint on the alignment (pre base 4.7.0).
type AlignBoundary (alg :: Nat) = SingI alg

alignmentBoundary :: AlignBoundary algn => Aligned algn a -> Int
alignmentBoundary = withSing aB
  where aB        ::  AlignBoundary algn => Sing algn      -> Aligned algn a -> Int
        aB algn _ = fromEnum $ fromSing algn


#endif


instance (Storable a, AlignBoundary alg) => Storable (Aligned alg a) where

  sizeOf = sizeOf . unAligned

  alignment alg = lcm valueAlignment forceAlignment
    where valueAlignment = alignment $ unAligned alg
          forceAlignment = alignmentBoundary alg

  peek = fmap Aligned .  peek . castPtr

  poke ptr = poke (castPtr ptr) . unAligned
