-- |
-- Module      : BlockN
-- License     : BSD-3-Clause
-- Copyright   : (c) 2025 Olivier Chéron
--
-- A secure block with length at type level
--
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
module BlockN
    ( BlockN, MutableBlockN, index, iterModify, mapEqPrimSize
    , BlockN.read, runNew, runThaw, runFold, BlockN.unsafeCast
    , BlockN.write, BlockN.zipWith
#ifdef ML_KEM_TESTING
    , create, BlockN.fromList, BlockN.replicate, BlockN.toList
#endif
    ) where

import Control.DeepSeq (NFData(..))
import Control.Monad.ST

import Data.Proxy

import Base
import Block (MutableBlock, blockRead, blockWrite, unsafeCastMut)
import Equality
import Fusion
import Marking (Classified, SecurityMarking)
import SecureBlock (SecureBlock)
import qualified SecureBlock
import Math

newtype BlockN marking (n :: Nat) a = BlockN { unBlockN :: SecureBlock marking a }

#ifdef ML_KEM_TESTING
instance (Classified marking, Eq a, PrimType a) => Eq (BlockN marking n a) where
    BlockN a == BlockN b = SecureBlock.eq a b

instance (Classified marking, PrimType a, Show a) => Show (BlockN marking n a) where
    showsPrec d = SecureBlock.showsPrec d . unBlockN
#endif

instance NFData (BlockN marking n a) where
    rnf = SecureBlock.toNormalForm . unBlockN

instance (Classified marking, KnownNat n, PrimType a, Add a) => Add (BlockN marking n a) where
    zero = BlockN.replicate zero
    {-# INLINE zero #-}
    (.+) = BlockN.zipWith (.+)
    {-# INLINE (.+) #-}
    (.-) = BlockN.zipWith (.-)
    {-# INLINE (.-) #-}
    neg = BlockN.map neg
    {-# INLINE neg #-}

newtype MutableBlockN (marking :: SecurityMarking) (n :: Nat) a m = MutableBlockN { unMutableBlockN :: MutableBlock a m }

instance (Classified marking, KnownNat n, PrimType a) => Fusion (BlockN marking n a) where
    type Mut (BlockN marking n a) s = MutableBlockN marking n a s
    newF = new Proxy
    thawF = thaw
    unsafeFreezeF = unsafeFreeze

-- Endomorphism specialization: a different implementation is substituted
-- wherever possible with rewrite rules.  Identical input and output types give
-- a chance for a transformation to be fused on an existing mutable block.

{-# RULES
"mapEndo" [~2] forall f. BlockN.map f = mapEndo f
"zipWithEndoL" [~2] forall f. BlockN.zipWith f = zipWithEndoL f
  #-}

index :: PrimType a => BlockN marking n a -> Offset a -> a
index = SecureBlock.index . unBlockN

replicate :: forall marking n a. (Classified marking, KnownNat n, PrimType a) => a -> BlockN marking n a
replicate = create . const

#ifdef ML_KEM_TESTING
fromList :: forall marking n a. (Classified marking, KnownNat n, PrimType a) => [a] -> Maybe (BlockN marking n a)
fromList elems
    | Prelude.length elems /= sz = Nothing
    | otherwise = Just $ runNew (Proxy :: Proxy marking) $ \mb -> go mb 0 elems
  where
    !sz = fromIntegral $ natVal (Proxy :: Proxy n)

    go !mb !i list = case list of
        []     -> return ()
        (x:xs) -> write mb i x >> go mb (i + 1) xs

toList :: PrimType a => BlockN marking n a -> [a]
toList = SecureBlock.toList . unBlockN
#endif

create :: forall marking n ty. (Classified marking, KnownNat n, PrimType ty)
       => (Offset ty -> ty)
       -> BlockN marking n ty
create initializer = runNew (Proxy :: Proxy marking) $ iterSet initializer
{-# INLINE create #-}

map :: (Classified marking, KnownNat n, PrimType a, PrimType b)
    => (a -> b) -> BlockN marking n a -> BlockN marking n b
map f (BlockN !a) = create $ \(Offset i) -> f (SecureBlock.index a (Offset i))
{-# INLINE [2] map #-}

mapEndo :: (Classified marking, KnownNat n, PrimType a)
        => (a -> a) -> BlockN marking n a -> BlockN marking n a
mapEndo = mapEqPrimSize
{-# INLINE mapEndo #-}

mapEqPrimSize :: (Classified marking, KnownNat n, EqPrimSize a b) => (a -> b) -> BlockN marking n a -> BlockN marking n b
mapEqPrimSize f = runContext . iterMapContext f . thawContext
{-# INLINE mapEqPrimSize #-}

iterModify :: forall marking n ty prim. (PrimType ty, KnownNat n, PrimMonad prim)
           => (ty -> ty)
           -> MutableBlockN marking n ty (PrimState prim)
           -> prim ()
iterModify f = iterModifyIx (\_ x -> f x)
{-# INLINE iterModify #-}

iterModifyIx :: forall marking n ty prim. (PrimType ty, KnownNat n, PrimMonad prim)
             => (Offset ty -> ty -> ty)
             -> MutableBlockN marking n ty (PrimState prim)
             -> prim ()
iterModifyIx f (MutableBlockN !ma) = loop 0
  where
    !sz = fromIntegral $ natVal (Proxy :: Proxy n)

    loop i
        | i .==# sz = pure ()
        | otherwise = blockRead ma i >>= \x -> blockWrite ma i (f i x) >> loop (i + 1)
{-# INLINE iterModifyIx #-}

iterSet :: forall marking n ty prim. (PrimType ty, KnownNat n, PrimMonad prim)
        => (Offset ty -> ty)
        -> MutableBlockN marking n ty (PrimState prim)
        -> prim ()
iterSet f (MutableBlockN !ma) = loop 0
  where
    !sz = fromIntegral $ natVal (Proxy :: Proxy n)

    loop i
        | i .==# sz = pure ()
        | otherwise = blockWrite ma i (f i) >> loop (i + 1)
{-# INLINE iterSet #-}

zipWith :: (Classified mc, KnownNat n, PrimType a, PrimType b, PrimType c)
        => (a -> b -> c) -> BlockN ma n a -> BlockN mb n b -> BlockN mc n c
zipWith f (BlockN !a) (BlockN !b) =
    create $ \(Offset i) ->
        f (SecureBlock.index a (Offset i)) (SecureBlock.index b (Offset i))
{-# INLINE [2] zipWith #-}

zipWithEndoL :: (Classified ma, KnownNat n, PrimType a, PrimType b)
             => (a -> b -> a) -> BlockN ma n a -> BlockN mb n b -> BlockN ma n a
zipWithEndoL f a b = runContext (seqContext b (iterMapIxContext g (thawContext a)))
  where g i x = f x (index b $ Offset i)
{-# INLINE zipWithEndoL #-}

unsafeCast :: BlockN marking n a -> SecureBlock marking b
unsafeCast = SecureBlock.unsafeCast . unBlockN

read :: (PrimMonad prim, PrimType a) => MutableBlockN marking n a (PrimState prim) -> Offset a -> prim a
read = blockRead . unMutableBlockN

write :: (PrimMonad prim, PrimType a) => MutableBlockN marking n a (PrimState prim) -> Offset a -> a -> prim ()
write = blockWrite . unMutableBlockN

new :: forall proxy marking n a prim. (Classified marking, KnownNat n, PrimMonad prim, PrimType a) => proxy marking -> prim (MutableBlockN marking n a (PrimState prim))
new prx = MutableBlockN <$> SecureBlock.new prx (CountOf sz)
  where !sz = fromIntegral $ natVal (Proxy :: Proxy n)
{-# INLINE new #-}

runThaw :: (Classified marking, KnownNat n, PrimType a) => BlockN marking n a -> (forall s. MutableBlockN marking n a s -> ST s ()) -> BlockN marking n a
runThaw a f = runContext (modifyContext f (thawContext a))
{-# INLINE runThaw #-}

runNew :: (Classified marking, KnownNat n, PrimType a) => proxy marking -> (forall s. MutableBlockN marking n a s -> ST s ()) -> BlockN marking n a
runNew _ f = runContext (modifyContext f newContext)
{-# INLINE runNew #-}

runFold :: (Classified marking, KnownNat n, PrimType a, Foldable t) => BlockN marking n a -> (forall s. b -> MutableBlockN marking n a s -> ST s ()) -> t b -> BlockN marking n a
runFold a f = runContext . foldContext f (thawContext a)
{-# INLINE runFold #-}

thaw :: (Classified marking, PrimMonad prim) => BlockN marking n a -> prim (MutableBlockN marking n a (PrimState prim))
thaw = fmap MutableBlockN . SecureBlock.thaw . unBlockN

unsafeFreeze :: (Classified marking, PrimMonad prim) => MutableBlockN marking n a (PrimState prim) -> prim (BlockN marking n a)
unsafeFreeze = fmap BlockN . SecureBlock.unsafeFreeze . unMutableBlockN

unsafeMapIx :: forall marking n a b prim. (KnownNat n, EqPrimSize a b, PrimMonad prim) => (Int -> a -> b) -> MutableBlockN marking n a (PrimState prim) -> prim (MutableBlockN marking n b (PrimState prim))
unsafeMapIx f (MutableBlockN !ma) = MutableBlockN . ensureEqPrimSize witness <$> loop 0
  where
    witness = undefined :: a -> b
    !sz = fromIntegral $ natVal (Proxy :: Proxy n)
    loop i
        | i == sz = return (unsafeCastMut ma)
        | otherwise = do
            a <- blockRead ma (Offset i)
            blockWrite (unsafeCastMut ma) (Offset i) (f i a)
            loop (i + 1)
{-# INLINE unsafeMapIx #-}

--

iterMapContext :: (EqPrimSize a b, Classified marking, KnownNat n) => (a -> b) -> Context (BlockN marking n a) -> Context (BlockN marking n b)
iterMapContext f = iterMapIxContext (\_ x -> f x)
{-# INLINE iterMapContext #-}

iterMapIxContext :: (EqPrimSize a b, Classified marking, KnownNat n) => (Int -> a -> b) -> Context (BlockN marking n a) -> Context (BlockN marking n b)
iterMapIxContext f = mapContext m
  where m = MapF { mapUpdate = unsafeMapIx f
                 , mapInit = \x -> newF >>= \mb -> iterSet (g x) mb >> return mb
                 }
        g x (Offset i) = f i (index x (Offset i))
{-# INLINE [1] iterMapIxContext #-}


-- Fusion rules
--
-- "iterMapIxContext/iterMapIxContext" merges element-wise transformations as
-- single operations.  For example @a .+ b .+ c@ becomes a single loop that
-- processes all input blocks in parallel and writes to the destination block.
--
-- "iterMapIxContext/seqContext" moves strictness annotations upstream so that
-- they do not prevent other rules from firing.

{-# RULES
"iterMapIxContext/seqContext" [~1] forall a f c. iterMapIxContext f (seqContext a c) = seqContext a (iterMapIxContext f c)
"iterMapIxContext/iterMapIxContext" [~1] forall f g c. iterMapIxContext f (iterMapIxContext g c) = iterMapIxContext (\i a -> f i (g i a)) c
  #-}
