{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}
module Avro.Decode.RawBlocksSpec
where

import Avro.Data.Endpoint
import Control.Monad                (forM_)
import Data.Avro                    (decodeContainerWithEmbeddedSchema, encodeContainerWithSchema, encodeValueWithSchema, nullCodec)
import Data.Avro.Internal.Container (decodeRawBlocks, packContainerBlocks, packContainerValues)
import Data.Either                  (rights)
import Data.List                    (unfoldr)
import Data.Semigroup               ((<>))
import Data.Text                    (pack)
import HaskellWorks.Hspec.Hedgehog
import Hedgehog
import Hedgehog.Range               (Range)
import Test.Hspec

import qualified Hedgehog.Gen   as Gen
import qualified Hedgehog.Range as Range

{- HLINT ignore "Reduce duplication"  -}
{- HLINT ignore "Redundant do"        -}

spec :: Spec
spec = describe "Avro.Decode.RawBlocksSpec" $ do

  it "should decode empty container" $ require $ withTests 1 $ property $ do
    empty <- evalIO $ encodeContainerWithSchema @Endpoint nullCodec schema'Endpoint []
    decoded <- evalEither $ decodeRawBlocks empty
    decoded === (schema'Endpoint, [])

  it "should decode container with one block" $ require $ withTests 5 $ property $ do
    msgs <- forAll $ Gen.list (Range.linear 1 5) endpointGen
    container <- evalIO $ encodeContainerWithSchema nullCodec schema'Endpoint [msgs]
    (s, bs)   <- evalEither $ decodeRawBlocks container

    s === schema'Endpoint
    blocks <- evalEither $ sequence bs
    fmap fst blocks === [length msgs]

  it "should decode container with multiple blocks" $ require $ withTests 20 $ property $ do
    msgs <- forAll $ Gen.list (Range.linear 1 19) endpointGen
    container <- evalIO $ encodeContainerWithSchema nullCodec schema'Endpoint (chunksOf 4 msgs)
    (s, bs)   <- evalEither $ decodeRawBlocks container

    s === schema'Endpoint
    blocks <- evalEither $ sequence bs

    let blockLengths = fst <$> blocks
    sum blockLengths === length msgs
    diff (last blockLengths) (<=) 4
    assert $ all (==4) (init blockLengths)

  it "should repack container" $ require $ withTests 20 $ property $ do
    srcValues <- forAll $ Gen.list (Range.linear 1 19) endpointGen
    srcContainer  <- evalIO $ encodeContainerWithSchema nullCodec schema'Endpoint (chunksOf 4 srcValues)
    (s, bs)       <- evalEither $ decodeRawBlocks srcContainer

    tgtContainer <- evalIO $ packContainerBlocks nullCodec s (rights bs)
    tgtValues <- evalEither . sequence $ decodeContainerWithEmbeddedSchema tgtContainer

    tgtValues === srcValues

  it "should pack container with individual values" $ require $ withTests 20 $ property $ do
    srcValues <- forAll $ Gen.list (Range.linear 1 19) endpointGen
    let values = encodeValueWithSchema schema'Endpoint <$> srcValues
    container <- evalIO $ packContainerValues nullCodec schema'Endpoint (chunksOf 4 values)

    (s, bs)  <- evalEither $ decodeRawBlocks container
    s === schema'Endpoint

    blocks <- evalEither $ sequence bs
    let blockLengths = fst <$> blocks
    diff (last blockLengths) (<=) 4
    assert $ all (==4) (init blockLengths)

    tgtValues <- evalEither . sequence $ decodeContainerWithEmbeddedSchema container
    tgtValues === srcValues

chunksOf :: Int -> [a] -> [[a]]
chunksOf n = takeWhile (not.null) . unfoldr (Just . splitAt n)
