module Codec.Picture.Png.Streaming.Util where
import           Control.Monad.Catch       (Exception, MonadThrow (..))
import           Control.Monad.Morph       (generalize)
import           Control.Monad.Trans       (MonadTrans (..))
import qualified Data.ByteString.Internal  as BI
import           Data.Functor.Identity     (Identity (..))
import           Data.Functor.Sum          (Sum (..))
import           Data.Int                  (Int64)
import           Data.Vector.Storable      (Vector)
import qualified Data.Vector.Storable      as Vec
import           Data.Word                 (Word8)
import           Data.ByteString.Streaming (ByteString)
import qualified Data.ByteString.Streaming as Q
import           Streaming                 (Stream)
import qualified Streaming                 as S
import           Streaming.Prelude         (Of (..))
import qualified Streaming.Prelude         as S
expectNull :: (MonadThrow m, Exception e) => e -> ByteString m r -> m r
expectNull err input =
  do headChunk <- Q.nextChunk input
     case headChunk of
       Left res -> return res
       Right _ -> throwM err
chunksOfBS :: (Monad m) => Int64 -> ByteString m r -> Stream (ByteString m) m r
chunksOfBS n input =
  do isEmpty <- lift (Q.null_ input)
     if isEmpty
       then lift (Q.effects input)
       else do rest <- S.yields (Q.splitAt n input)
               chunksOfBS n rest
rememberPrevious :: (Monad m) => Stream (Of a) m r -> Stream (Of (Maybe a, a)) m r
rememberPrevious = go Nothing
  where
    go prevItem input =
      let continue (thisItem, rest) =
            do S.yields ((prevItem, thisItem) :> ())
               go (Just thisItem) rest
      in lift (S.next input) >>= either return continue
mapWithMemory :: forall m a b r. (Monad m) => (Maybe b -> a -> m b) -> Stream (Of a) m r -> Stream (Of b) m r
mapWithMemory f input = S.unfoldr step (Nothing, input)
  where
    step :: (Maybe b, Stream (Of a) m r) -> m (Either r (b, (Maybe b, Stream (Of a) m r)))
    step (prevResult, stream) =
      let handleItem (thisItem, remainder) =
             do thisResult <- f prevResult thisItem
                return (Right (thisResult, (Just thisResult, remainder)))
      in S.next stream >>= either (return . Left) handleItem
filterMapped
  :: (Monad m, Functor f, Functor g)
     => (forall x. f x -> m (Sum Identity g x))
     -> Stream f m r
     -> Stream g m r
filterMapped f = S.run . S.maps generalize . S.separate . S.mapped f
filtered
  :: (Monad m, Functor f)
     => (forall x. f x -> m (Maybe x))
     -> Stream f m r
     -> Stream f m r
filtered f =
  let f' x = maybe (InR x) (InL . Identity) <$> f x
  in filterMapped f'
buildByteString
  :: Monad m
     => (a -> m (Either r (ByteString m (), a)))
     -> a
     -> ByteString m r
buildByteString build seed =
  do mx <- lift $ build seed
     case mx of
       Left r -> return r
       Right (bs, seed') ->
         do bs
            buildByteString build seed'
bytestringToVector :: BI.ByteString -> Vector Word8
bytestringToVector (BI.PS fptr offset idx) = Vec.unsafeFromForeignPtr fptr offset idx
vectorToBytestring :: Vector Word8 -> BI.ByteString
vectorToBytestring v
  | (fptr, offset, idx) <- Vec.unsafeToForeignPtr v =
    BI.PS fptr offset idx