-- |
-- Module      : Block
-- License     : BSD-3-Clause
-- Copyright   : (c) 2025 Olivier Chéron
--
-- An array of primitive (unlifted) elements.  This module exposes the
-- t'PrimArray' implementation from primitive but through an API similar to
-- basement @Block@.
--
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
module Block
    ( Block, MutableBlock, blockIndex, blockRead, blockWrite
    , foldZipWith, Block.length, mutableContents
    , Block.new, Block.newPinned, Block.thaw, thawPinned
    , Block.unsafeCast, unsafeCastMut, Block.unsafeFreeze, Block.unsafeThaw
#ifdef ML_KEM_TESTING
    , Block.toList
#endif
    ) where

import Control.Monad.Primitive

import Data.Primitive.ByteArray
import Data.Primitive.PrimArray

import Control.Exception (assert)

import Foreign.Ptr (Ptr)

import Base

type Block = PrimArray
type MutableBlock ty s = MutablePrimArray s ty

blockIndex :: PrimType ty => Block ty -> Offset ty -> ty
blockRead :: (PrimMonad prim, PrimType ty) => MutableBlock ty (PrimState prim) -> Offset ty -> prim ty
blockWrite :: (PrimMonad prim, PrimType ty) => MutableBlock ty (PrimState prim) -> Offset ty -> ty -> prim ()
#ifdef ML_KEM_TESTING
blockIndex b off@(Offset i) =
    checkBounds (Block.length b) off $ indexPrimArray b i
blockRead mb off@(Offset i) = getSizeofMutablePrimArray mb >>= \sz ->
    checkBounds (CountOf sz) off $ readPrimArray mb i
blockWrite mb off@(Offset i) a = getSizeofMutablePrimArray mb >>= \sz ->
    checkBounds (CountOf sz) off $ writePrimArray mb i a
#else
blockIndex b (Offset i) = indexPrimArray b i
blockRead mb (Offset i) = readPrimArray mb i
blockWrite mb (Offset i) = writePrimArray mb i
#endif

foldZipWith :: (PrimType a, PrimType b)
            => (c -> a -> b -> c) -> c -> Block a -> Block b -> c
foldZipWith f c a b = assert (sa == sb) $
    loop c 0
  where
    sa = sizeofPrimArray a
    sb = sizeofPrimArray b

    loop !acc i
        | i == sa = acc
        | otherwise = do
            let va = indexPrimArray a i
            let vb = indexPrimArray b i
            loop (f acc va vb) (i + 1)
{-# INLINE foldZipWith #-}

length :: PrimType ty => Block ty -> CountOf ty
length = CountOf . sizeofPrimArray

mutableContents :: MutableBlock ty s -> Ptr ty
mutableContents = mutablePrimArrayContents -- pinned only

new :: (PrimMonad prim, PrimType ty) => CountOf ty -> prim (MutableBlock ty (PrimState prim))
new (CountOf n) = newPrimArray n

newPinned :: (PrimMonad prim, PrimType ty) => CountOf ty -> prim (MutableBlock ty (PrimState prim))
newPinned (CountOf n) = newPinnedPrimArray n

thaw :: PrimMonad prim => Block ty -> prim (MutableBlock ty (PrimState prim))
thaw (PrimArray !barr) = unsafeSTToPrim $
    -- as optimization, combine both steps in a known monad and avoid
    -- round trip between byte length and element count
    thawByteArray ba 0 (sizeofByteArray ba) >>= \(MutableByteArray mbarr) ->
        return (MutablePrimArray mbarr)
  where ba = ByteArray barr

thawPinned :: PrimMonad prim => Block ty -> prim (MutableBlock ty (PrimState prim))
thawPinned (PrimArray !barr) = unsafeSTToPrim $ do
    let ba = ByteArray barr
        n = sizeofByteArray ba
    mb@(MutableByteArray mbarr) <- newPinnedByteArray n
    copyByteArray mb 0 ba 0 n
    return (MutablePrimArray mbarr)

#ifdef ML_KEM_TESTING
toList :: PrimType ty => Block ty -> [ty]
toList = primArrayToList
#endif

unsafeCast :: Block a -> Block b
unsafeCast (PrimArray b) = PrimArray b

unsafeCastMut :: MutableBlock a m -> MutableBlock b m
unsafeCastMut (MutablePrimArray mb) = MutablePrimArray mb

unsafeFreeze :: PrimMonad prim => MutableBlock ty (PrimState prim) -> prim (Block ty)
unsafeFreeze = unsafeFreezePrimArray

unsafeThaw :: PrimMonad prim => Block ty -> prim (MutableBlock ty (PrimState prim))
unsafeThaw = unsafeThawPrimArray
