{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

module HaskellWorks.Data.Json.Simd.Index.Standard
  ( makeStandardJsonIbBps
  , makeStandardJsonIbBpsUnsafe
  , enabledMakeStandardJsonIbBps
  ) where

import Control.Monad
import Data.Word
import HaskellWorks.Data.Json.Simd.Internal.Index.Standard

import qualified Data.ByteString                              as BS
import qualified Data.ByteString.Internal                     as BSI
import qualified Data.ByteString.Lazy                         as LBS
import qualified Foreign.ForeignPtr                           as F
import qualified Foreign.ForeignPtr.Unsafe                    as F
import qualified Foreign.Marshal.Unsafe                       as F
import qualified Foreign.Ptr                                  as F
import qualified Foreign.Storable                             as F
import qualified HaskellWorks.Data.Json.Simd.Capabilities     as C
import qualified HaskellWorks.Data.Json.Simd.Internal.Foreign as F
import qualified System.IO.Unsafe                             as IO

{-# ANN module ("HLint: ignore Reduce duplication"  :: String) #-}
{-# ANN module ("HLint: ignore Redundant do"        :: String) #-}

makeStandardJsonIbBps :: LBS.ByteString -> Either String [(BS.ByteString, BS.ByteString)]
makeStandardJsonIbBps lbs = if enabledMakeStandardJsonIbBps
  then Right (makeStandardJsonIbBpsUnsafe lbs)
  else Left "makeStandardJsonIbBps function is disabled"

makeStandardJsonIbBpsUnsafe :: LBS.ByteString -> [(BS.ByteString, BS.ByteString)]
makeStandardJsonIbBpsUnsafe lbs = F.unsafeLocalState $ do
  wb <- allocWorkBuffers (32 * 1024 * 1204)
  ws <- newWorkState 0
  fptrState       :: F.ForeignPtr F.UInt32  <- F.mallocForeignPtr
  fptrRemBits     :: F.ForeignPtr F.UInt64  <- F.mallocForeignPtr
  fptrRemBitsLen  :: F.ForeignPtr F.Size    <- F.mallocForeignPtr
  let ptrState      = F.unsafeForeignPtrToPtr fptrState
  let ptrRemBits    = F.unsafeForeignPtrToPtr fptrRemBits
  let ptrRemBitsLen = F.unsafeForeignPtrToPtr fptrRemBitsLen
  F.poke ptrState       0
  F.poke ptrRemBits     0
  F.poke ptrRemBitsLen  0
  IO.unsafeInterleaveIO $ go wb ws fptrState fptrRemBits fptrRemBitsLen (LBS.toChunks lbs)
  where go :: ()
          => WorkBuffers
          -> WorkState
          -> F.ForeignPtr F.UInt32
          -> F.ForeignPtr F.UInt64
          -> F.ForeignPtr F.Size
          -> [BS.ByteString]
          -> IO [(BS.ByteString, BS.ByteString)]
        go _ _ _ fptrRemBits fptrRemBitsLen  []       = do
          resBpFptr  <- F.mallocForeignPtrBytes 8
          let resBpPtr      = F.castPtr (F.unsafeForeignPtrToPtr resBpFptr  )
          let ptrRemBits    = F.unsafeForeignPtrToPtr fptrRemBits
          let ptrRemBitsLen = F.unsafeForeignPtrToPtr fptrRemBitsLen
          remBits     <- F.peek ptrRemBits
          remBitsLen  <- F.peek ptrRemBitsLen
          bpByteLen <- F.smWriteBpChunkFinal
            remBits     -- remaining_bp_bits
            remBitsLen  -- remaning_bp_bits_len
            resBpPtr    -- out_buffer
          return  [ ( BS.empty
                    , BSI.fromForeignPtr resBpFptr 0 (fromIntegral bpByteLen * 8)
                    )
                  ]
        go wb ws fptrState fptrRemBits fptrRemBitsLen (bs:bss) = do
          let (!bsFptr, !bsOff, !bsLen) = BSI.toForeignPtr bs
          let !idxByteLen = (bsLen + 7) `div` 8
          resIbFptr  <- F.mallocForeignPtrBytes idxByteLen
          resBpFptr  <- F.mallocForeignPtrBytes idxByteLen
          let resIbPtr      = F.castPtr (F.unsafeForeignPtrToPtr resIbFptr  )
          let resBpPtr      = F.castPtr (F.unsafeForeignPtrToPtr resBpFptr  )
          let bsPtr         = F.castPtr (F.unsafeForeignPtrToPtr bsFptr)
          let ptrState      = F.unsafeForeignPtrToPtr fptrState
          let ptrRemBits    = F.unsafeForeignPtrToPtr fptrRemBits
          let ptrRemBitsLen = F.unsafeForeignPtrToPtr fptrRemBitsLen
          s :: Word8 <- fromIntegral <$> F.peek ptrState
          void $ F.smProcessChunk
            (F.plusPtr bsPtr bsOff) -- in_buffer:   Ptr UInt8
            (fromIntegral bsLen)    -- in_length:   Size
            ptrState                -- work state:  Ptr UInt32
            (workBuffersP wb)       -- result_phi:  Ptr UInt8
          void $ F.smMakeIbOpClChunks
            (fromIntegral s)          -- state
            (workBuffersP wb)         -- in_phis
            (fromIntegral bsLen)      -- phi_length
            resIbPtr                  -- out_ibs
            (workBuffersO wb)         -- out_ops
            (workBuffersC wb)         -- out_cls
          bpByteLen <- F.smWriteBpChunk
            (workBuffersO wb)         -- result_op
            (workBuffersC wb)         -- result_cl
            (fromIntegral idxByteLen) -- ib_bytes
            ptrRemBits                -- remaining_bp_bits
            ptrRemBitsLen             -- remaning_bp_bits_len
            resBpPtr                  -- out_buffer
          let !r =
                ( BSI.fromForeignPtr resIbFptr 0 idxByteLen
                , BSI.fromForeignPtr resBpFptr 0 (fromIntegral bpByteLen * 8)
                )
          rs <- IO.unsafeInterleaveIO $ go wb ws fptrState fptrRemBits fptrRemBitsLen bss
          return (r:rs)

enabledMakeStandardJsonIbBps :: Bool
enabledMakeStandardJsonIbBps = C.avx_2 && C.sse_4_2 && C.bmi_2