{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# OPTIONS_GHC -fno-warn-orphans #-}
module BufferTest (main) where

import Control.Monad.ST
import Control.Monad.ST.Unsafe
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BS
import Data.Word (Word8)
import Foreign.Marshal.Utils
import Foreign.Ptr (castPtr)
import Test.HUnit
import Test.QuickCheck

import Facebook.Init
import TestRunner
import Util.Testing

import qualified Util.Buffer as Buffer

data FillStep
  = FillByte Word8
  | FillByteString ByteString
  | FillAlloc Int ByteString
  deriving(Show)

stepToBS :: FillStep -> ByteString
stepToBS (FillByte x) = BS.singleton x
stepToBS (FillByteString x) = x
stepToBS (FillAlloc _ x) = x

step :: FillStep -> Buffer.Fill s ()
step (FillByte x) = Buffer.byte x
step (FillByteString x) = Buffer.byteString x
step (FillAlloc n x) = Buffer.alloc n $ \p -> unsafeIOToST $
  BS.unsafeUseAsCStringLen x $ \(q,k) -> do
    copyBytes p (castPtr q) k
    return k

instance Arbitrary ByteString where
  arbitrary = BS.pack <$> arbitrary

instance Arbitrary FillStep where
  arbitrary = oneof
    [ FillByte <$> arbitrary
    , FillByteString <$> arbitrary
    , (\(NonNegative n) s -> FillAlloc (BS.length s + n) s)
        <$> arbitrary
        <*> arbitrary
    ]

prop_fillByteString :: NonEmptyList (NonEmptyList FillStep) -> Property
prop_fillByteString ss =
  runST (Buffer.fillByteString 1
    $ foldr1 (\p q -> p >>= \_ -> q)
    $ map (foldr1 (>>) . map step) stepss
    )
  ===
  mconcat (map stepToBS $ concat stepss)
  where
    stepss = getNonEmpty <$> getNonEmpty ss

main :: IO ()
main = withFacebookUnitTest $ testRunner $ TestList
  [ TestLabel "fillByteString" $ TestCase $ assertProperty "mismatch"
      prop_fillByteString
  ]