package gs.std.v0.effect.pipes

import cats.Applicative
import fs2.Chunk
import fs2.Pipe
import fs2.Pull
import fs2.Stream
import gs.std.v0.core.SHA256
import java.security.MessageDigest

class SHA256Pipe[F[_]: Applicative] extends Pipe[F, Byte, SHA256]:

  private def newDigest(): MessageDigest =
    MessageDigest.getInstance(SHA256.Algorithm)

  private def append(
    digest: MessageDigest,
    data: Chunk[Byte]
  ): F[Unit] =
    Applicative[F].pure(digest.update(data.toArray))

  def pull(
    s: Stream[F, Byte],
    digest: MessageDigest
  ): Pull[F, SHA256, Unit] =
    s.pull.uncons.flatMap {
      case Some((head, tail)) =>
        // There is another chunk of data.
        // Update the digest and then handle the next chunk.
        Pull.eval(append(digest, head)) >> pull(tail, digest)
      case None =>
        // The stream is empty. Emit a single chunk that contains the calculated
        // hash.
        Pull.output(Chunk(SHA256.fromBytes(digest.digest()))) >> Pull.done
    }

  override def apply(v1: Stream[F, Byte]): Stream[F, SHA256] =
    pull(v1, newDigest()).stream

end SHA256Pipe

object SHA256Pipe:

  def apply[F[_]: Applicative] = new SHA256Pipe[F]

end SHA256Pipe
