/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.core

import android.content.ContentResolver
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import com.facebook.common.internal.Preconditions
import com.facebook.common.media.MediaUtils.isVideo
import com.facebook.common.memory.PooledByteBuffer
import com.facebook.common.references.CloseableReference
import com.facebook.common.webp.WebpSupportStatus
import com.facebook.imagepipeline.common.SourceUriType
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.producers.CustomProducerSequenceFactory
import com.facebook.imagepipeline.producers.NetworkFetcher
import com.facebook.imagepipeline.producers.Producer
import com.facebook.imagepipeline.producers.RemoveImageTransformMetaDataProducer
import com.facebook.imagepipeline.producers.ThreadHandoffProducerQueue
import com.facebook.imagepipeline.producers.ThumbnailProducer
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.systrace.FrescoSystrace.traceSection
import com.facebook.imagepipeline.transcoder.ImageTranscoderFactory

class ProducerSequenceFactory(
    private val contentResolver: ContentResolver,
    private val producerFactory: ProducerFactory,
    private val networkFetcher: NetworkFetcher<*>,
    private val resizeAndRotateEnabledForNetwork: Boolean,
    private val webpSupportEnabled: Boolean,
    private val threadHandoffProducerQueue: ThreadHandoffProducerQueue,
    private val downSampleEnabled: Boolean,
    private val useBitmapPrepareToDraw: Boolean,
    private val partialImageCachingEnabled: Boolean,
    private val diskCacheEnabled: Boolean,
    private val imageTranscoderFactory: ImageTranscoderFactory,
    private val isEncodedMemoryCacheProbingEnabled: Boolean,
    private val isDiskCacheProbingEnabled: Boolean,
    private val allowDelay: Boolean,
    private val customProducerSequenceFactories: Set<CustomProducerSequenceFactory>?
) {

  @VisibleForTesting
  var postprocessorSequences:
      MutableMap<
          Producer<CloseableReference<CloseableImage>>,
          Producer<CloseableReference<CloseableImage>>> =
      mutableMapOf()

  @VisibleForTesting
  var closeableImagePrefetchSequences:
      MutableMap<Producer<CloseableReference<CloseableImage>>, Producer<Void?>> =
      mutableMapOf()

  @VisibleForTesting
  var bitmapPrepareSequences:
      MutableMap<
          Producer<CloseableReference<CloseableImage>>,
          Producer<CloseableReference<CloseableImage>>> =
      mutableMapOf()

  /**
   * Returns a sequence that can be used for a request for an encoded image from either network or
   * local files.
   *
   * @param imageRequest the request that will be submitted
   * @return the sequence that should be used to process the request
   */
  fun getEncodedImageProducerSequence(
      imageRequest: ImageRequest
  ): Producer<CloseableReference<PooledByteBuffer>> =
      traceSection("ProducerSequenceFactory#getEncodedImageProducerSequence") {
        validateEncodedImageRequest(imageRequest)
        val uri = imageRequest.sourceUri
        when (imageRequest.sourceUriType) {
          SourceUriType.SOURCE_TYPE_NETWORK -> networkFetchEncodedImageProducerSequence
          SourceUriType.SOURCE_TYPE_LOCAL_VIDEO_FILE,
          SourceUriType.SOURCE_TYPE_LOCAL_IMAGE_FILE -> localFileFetchEncodedImageProducerSequence
          SourceUriType.SOURCE_TYPE_LOCAL_CONTENT ->
              localContentUriFetchEncodedImageProducerSequence
          else ->
              throw IllegalArgumentException(
                  "Unsupported uri scheme for encoded image fetch! Uri is: " +
                      getShortenedUriString(uri))
        }
      }

  /** Returns a sequence that can be used for a request for an encoded image from network. */
  val networkFetchEncodedImageProducerSequence:
      Producer<CloseableReference<PooledByteBuffer>> by lazy {
    traceSection("ProducerSequenceFactory#getNetworkFetchEncodedImageProducerSequence:init") {
      RemoveImageTransformMetaDataProducer(backgroundNetworkFetchToEncodedMemorySequence)
    }
  }

  /** Returns a sequence that can be used for a request for an encoded image from a local file. */
  @VisibleForTesting
  val localFileFetchEncodedImageProducerSequence:
      Producer<CloseableReference<PooledByteBuffer>> by lazy {
    traceSection("ProducerSequenceFactory#getLocalFileFetchEncodedImageProducerSequence:init") {
      RemoveImageTransformMetaDataProducer(backgroundLocalFileFetchToEncodeMemorySequence)
    }
  }

  /**
   * Returns a sequence that can be used for a request for an encoded image from a local content
   * uri.
   */
  val localContentUriFetchEncodedImageProducerSequence:
      Producer<CloseableReference<PooledByteBuffer>> by lazy {
    traceSection(
        "ProducerSequenceFactory#getLocalContentUriFetchEncodedImageProducerSequence:init") {
          RemoveImageTransformMetaDataProducer(backgroundLocalContentUriFetchToEncodeMemorySequence)
        }
  }

  /**
   * Returns a sequence that can be used for a prefetch request for an encoded image.
   *
   * Guaranteed to return the same sequence as `getEncodedImageProducerSequence(request)`, except
   * that it is pre-pended with a [SwallowResultProducer].
   *
   * @param imageRequest the request that will be submitted
   * @return the sequence that should be used to process the request
   */
  fun getEncodedImagePrefetchProducerSequence(imageRequest: ImageRequest): Producer<Void?> {
    validateEncodedImageRequest(imageRequest)
    return when (imageRequest.sourceUriType) {
      SourceUriType.SOURCE_TYPE_NETWORK -> networkFetchToEncodedMemoryPrefetchSequence
      SourceUriType.SOURCE_TYPE_LOCAL_VIDEO_FILE,
      SourceUriType.SOURCE_TYPE_LOCAL_IMAGE_FILE -> localFileFetchToEncodedMemoryPrefetchSequence
      else -> {
        val uri = imageRequest.sourceUri
        throw IllegalArgumentException(
            "Unsupported uri scheme for encoded image fetch! Uri is: " + getShortenedUriString(uri))
      }
    }
  }

  /**
   * Returns a sequence that can be used for a request for a decoded image.
   *
   * @param imageRequest the request that will be submitted
   * @return the sequence that should be used to process the request
   */
  fun getDecodedImageProducerSequence(
      imageRequest: ImageRequest
  ): Producer<CloseableReference<CloseableImage>> =
      traceSection("ProducerSequenceFactory#getDecodedImageProducerSequence") {
        var pipelineSequence = getBasicDecodedImageSequence(imageRequest)
        if (imageRequest.postprocessor != null) {
          pipelineSequence = getPostprocessorSequence(pipelineSequence)
        }
        if (useBitmapPrepareToDraw) {
          pipelineSequence = getBitmapPrepareSequence(pipelineSequence)
        }
        if (allowDelay && imageRequest.delayMs > 0) {
          pipelineSequence = getDelaySequence(pipelineSequence)
        }
        return pipelineSequence
      }

  /**
   * Returns a sequence that can be used for a prefetch request for a decoded image.
   *
   * @param imageRequest the request that will be submitted
   * @return the sequence that should be used to process the request
   */
  fun getDecodedImagePrefetchProducerSequence(imageRequest: ImageRequest): Producer<Void?> {
    var inputProducer = getBasicDecodedImageSequence(imageRequest)
    if (useBitmapPrepareToDraw) {
      inputProducer = getBitmapPrepareSequence(inputProducer)
    }
    return getDecodedImagePrefetchSequence(inputProducer)
  }

  private fun getBasicDecodedImageSequence(
      imageRequest: ImageRequest
  ): Producer<CloseableReference<CloseableImage>> =
      traceSection("ProducerSequenceFactory#getBasicDecodedImageSequence") {
        val uri = imageRequest.sourceUri
        checkNotNull(uri) { "Uri is null." }
        when (imageRequest.sourceUriType) {
          SourceUriType.SOURCE_TYPE_NETWORK -> networkFetchSequence
          SourceUriType.SOURCE_TYPE_LOCAL_VIDEO_FILE -> localVideoFileFetchSequence
          SourceUriType.SOURCE_TYPE_LOCAL_IMAGE_FILE -> localImageFileFetchSequence
          SourceUriType.SOURCE_TYPE_LOCAL_CONTENT -> {
            if (imageRequest.loadThumbnailOnlyForAndroidSdkAboveQ) {
              return localContentUriThumbnailFetchSequence
            } else if (isVideo(contentResolver.getType(uri))) {
              return localVideoFileFetchSequence
            }
            localContentUriFetchSequence
          }
          SourceUriType.SOURCE_TYPE_LOCAL_ASSET -> localAssetFetchSequence
          SourceUriType.SOURCE_TYPE_LOCAL_RESOURCE -> localResourceFetchSequence
          SourceUriType.SOURCE_TYPE_QUALIFIED_RESOURCE -> qualifiedResourceFetchSequence
          SourceUriType.SOURCE_TYPE_DATA -> dataFetchSequence
          else -> {
            if (customProducerSequenceFactories != null) {
              for (customProducerSequenceFactory in customProducerSequenceFactories) {
                val sequence =
                    customProducerSequenceFactory.getCustomDecodedImageSequence(imageRequest, this)
                if (sequence != null) {
                  return sequence
                }
              }
            }
            throw IllegalArgumentException(
                "Unsupported uri scheme! Uri is: " + getShortenedUriString(uri))
          }
        }
      }

  /**
   * swallow result if prefetch -> bitmap cache get -> background thread hand-off -> multiplex ->
   * bitmap cache -> decode -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
   * network fetch.
   */
  val networkFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    traceSection("ProducerSequenceFactory#getNetworkFetchSequence:init") {
      newBitmapCacheGetToDecodeSequence(commonNetworkFetchToEncodedMemorySequence)
    }
  }

  /**
   * background-thread hand-off -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
   * network fetch.
   */
  val backgroundNetworkFetchToEncodedMemorySequence: Producer<EncodedImage?> by lazy {
    traceSection("ProducerSequenceFactory#getBackgroundNetworkFetchToEncodedMemorySequence:init") {
      // Use hand-off producer to ensure that we don't do any unnecessary work on the UI thread.
      producerFactory.newBackgroundThreadHandoffProducer(
          commonNetworkFetchToEncodedMemorySequence, threadHandoffProducerQueue)
    }
  }

  /**
   * swallow-result -> background-thread hand-off -> multiplex -> encoded cache -> disk cache ->
   * (webp transcode) -> network fetch.
   */
  val networkFetchToEncodedMemoryPrefetchSequence: Producer<Void?> by lazy {
    traceSection("ProducerSequenceFactory#getNetworkFetchToEncodedMemoryPrefetchSequence") {
      producerFactory.newSwallowResultProducer(backgroundNetworkFetchToEncodedMemorySequence)
    }
  }

  /**
   * multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch. Alternatively,
   * multiplex -> combined network and cache
   */
  val commonNetworkFetchToEncodedMemorySequence: Producer<EncodedImage> by lazy {
    traceSection("ProducerSequenceFactory#getCommonNetworkFetchToEncodedMemorySequence") {
      newCommonNetworkFetchToEncodedMemorySequence(networkFetcher)
    }
  }

  @Synchronized
  fun newCommonNetworkFetchToEncodedMemorySequence(
      networkFetcher: NetworkFetcher<*>
  ): Producer<EncodedImage> =
      traceSection("ProducerSequenceFactory#createCommonNetworkFetchToEncodedMemorySequence") {
        val inputProducer: Producer<EncodedImage> =
            newEncodedCacheMultiplexToTranscodeSequence(
                producerFactory.newNetworkFetchProducer(networkFetcher))
        var networkFetchToEncodedMemorySequence: Producer<EncodedImage?> =
            ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer)
        networkFetchToEncodedMemorySequence =
            producerFactory.newResizeAndRotateProducer(
                networkFetchToEncodedMemorySequence,
                resizeAndRotateEnabledForNetwork && !downSampleEnabled,
                imageTranscoderFactory)
        return networkFetchToEncodedMemorySequence
      }

  /**
   * swallow-result -> background-thread hand-off -> multiplex -> encoded cache -> disk cache ->
   * (webp transcode) -> local file fetch.
   */
  val localFileFetchToEncodedMemoryPrefetchSequence: Producer<Void?> by lazy {
    traceSection("ProducerSequenceFactory#getLocalFileFetchToEncodedMemoryPrefetchSequence:init") {
      producerFactory.newSwallowResultProducer(backgroundLocalFileFetchToEncodeMemorySequence)
    }
  }

  /**
   * background-thread hand-off -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
   * local file fetch
   */
  val backgroundLocalFileFetchToEncodeMemorySequence: Producer<EncodedImage?> by lazy {
    traceSection("ProducerSequenceFactory#getBackgroundLocalFileFetchToEncodeMemorySequence") {
      val localFileFetchProducer = producerFactory.newLocalFileFetchProducer()
      val toEncodedMultiplexProducer =
          newEncodedCacheMultiplexToTranscodeSequence(localFileFetchProducer)
      producerFactory.newBackgroundThreadHandoffProducer(
          toEncodedMultiplexProducer, threadHandoffProducerQueue)
    }
  }

  /**
   * background-thread hand-off -> multiplex -> encoded cache -> disk cache -> (webp transcode) ->
   * local content resolver fetch
   */
  val backgroundLocalContentUriFetchToEncodeMemorySequence: Producer<EncodedImage?> by lazy {
    traceSection(
        "ProducerSequenceFactory#getBackgroundLocalContentUriFetchToEncodeMemorySequence:init") {
          val localFileFetchProducer = producerFactory.newLocalContentUriFetchProducer()
          val toEncodedMultiplexProducer =
              newEncodedCacheMultiplexToTranscodeSequence(localFileFetchProducer)
          producerFactory.newBackgroundThreadHandoffProducer(
              toEncodedMultiplexProducer, threadHandoffProducerQueue)
        }
  }

  /**
   * bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> decode -> branch
   * on separate images -> exif resize and rotate -> exif thumbnail creation -> local image resize
   * and rotate -> add meta data producer -> multiplex -> encoded cache -> (webp transcode) -> local
   * file fetch.
   */
  val localImageFileFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    val localFileFetchProducer = producerFactory.newLocalFileFetchProducer()
    newBitmapCacheGetToLocalTransformSequence(localFileFetchProducer)
  }

  /** Bitmap cache get -> thread hand off -> multiplex -> bitmap cache -> local video thumbnail */
  val localVideoFileFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    val localVideoThumbnailProducer = producerFactory.newLocalVideoThumbnailProducer()
    newBitmapCacheGetToBitmapCacheSequence(localVideoThumbnailProducer)
  }

  /**
   * bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> decode -> branch
   * on separate images -> thumbnail resize and rotate -> thumbnail branch -> local content
   * thumbnail creation -> exif thumbnail creation -> local image resize and rotate -> add meta data
   * producer -> multiplex -> encoded cache -> (webp transcode) -> local content uri fetch.
   */
  val localContentUriFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    val localContentUriFetchProducer = producerFactory.newLocalContentUriFetchProducer()
    val thumbnailProducers: Array<ThumbnailProducer<EncodedImage>> =
        arrayOf(
            producerFactory.newLocalContentUriThumbnailFetchProducer(),
            producerFactory.newLocalExifThumbnailProducer(),
        )
    newBitmapCacheGetToLocalTransformSequence(localContentUriFetchProducer, thumbnailProducers)
  }

  /**
   * bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> local thumbnail
   * bitmap
   */
  @get:RequiresApi(Build.VERSION_CODES.Q)
  val localContentUriThumbnailFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
      newBitmapCacheGetToBitmapCacheSequence(producerFactory.newLocalThumbnailBitmapProducer())
    } else {
      throw Throwable("Unreachable exception. Just to make linter happy for the lazy block.")
    }
  }

  /**
   * bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> decode -> branch
   * on separate images -> exif resize and rotate -> exif thumbnail creation -> local image resize
   * and rotate -> add meta data producer -> multiplex -> encoded cache -> (webp transcode) ->
   * qualified resource fetch.
   */
  val qualifiedResourceFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    val qualifiedResourceFetchProducer = producerFactory.newQualifiedResourceFetchProducer()
    newBitmapCacheGetToLocalTransformSequence(qualifiedResourceFetchProducer)
  }

  /**
   * bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> decode -> branch
   * on separate images -> exif resize and rotate -> exif thumbnail creation -> local image resize
   * and rotate -> add meta data producer -> multiplex -> encoded cache -> (webp transcode) -> local
   * resource fetch.
   */
  val localResourceFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    val localResourceFetchProducer = producerFactory.newLocalResourceFetchProducer()
    newBitmapCacheGetToLocalTransformSequence(localResourceFetchProducer)
  }

  /**
   * bitmap cache get -> background thread hand-off -> multiplex -> bitmap cache -> decode -> branch
   * on separate images -> exif resize and rotate -> exif thumbnail creation -> local image resize
   * and rotate -> add meta data producer -> multiplex -> encoded cache -> (webp transcode) -> local
   * asset fetch.
   */
  val localAssetFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    val localAssetFetchProducer = producerFactory.newLocalAssetFetchProducer()
    newBitmapCacheGetToLocalTransformSequence(localAssetFetchProducer)
  }

  /**
   * bitmap cache get -> background thread hand-off -> bitmap cache -> decode -> resize and rotate
   * -> (webp transcode) -> data fetch.
   */
  val dataFetchSequence: Producer<CloseableReference<CloseableImage>> by lazy {
    var inputProducer: Producer<EncodedImage?> = producerFactory.newDataFetchProducer()
    if (WebpSupportStatus.sIsWebpSupportRequired &&
        (!webpSupportEnabled || WebpSupportStatus.sWebpBitmapFactory == null)) {
      inputProducer = producerFactory.newWebpTranscodeProducer(inputProducer)
    }
    inputProducer = ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer)
    inputProducer =
        producerFactory.newResizeAndRotateProducer(inputProducer, true, imageTranscoderFactory)
    newBitmapCacheGetToDecodeSequence(inputProducer)
  }

  /**
   * Creates a new fetch sequence that just needs the source producer.
   *
   * @param inputProducer the source producer
   * @return the new sequence
   */
  private fun newBitmapCacheGetToLocalTransformSequence(
      inputProducer: Producer<EncodedImage>
  ): Producer<CloseableReference<CloseableImage>> {
    val defaultThumbnailProducers: Array<ThumbnailProducer<EncodedImage>> =
        arrayOf(producerFactory.newLocalExifThumbnailProducer())
    return newBitmapCacheGetToLocalTransformSequence(inputProducer, defaultThumbnailProducers)
  }

  /**
   * Creates a new fetch sequence that just needs the source producer.
   *
   * @param inputProducer the source producer
   * @param thumbnailProducers the thumbnail producers from which to request the image before
   *   falling back to the full image producer sequence
   * @return the new sequence
   */
  private fun newBitmapCacheGetToLocalTransformSequence(
      inputProducer: Producer<EncodedImage>,
      thumbnailProducers: Array<ThumbnailProducer<EncodedImage>>
  ): Producer<CloseableReference<CloseableImage>> {
    var ip = inputProducer
    ip = newEncodedCacheMultiplexToTranscodeSequence(ip)
    ip = newLocalTransformationsSequence(ip, thumbnailProducers)
    return newBitmapCacheGetToDecodeSequence(ip)
  }

  /**
   * Same as `newBitmapCacheGetToBitmapCacheSequence` but with an extra DecodeProducer.
   *
   * @param inputProducer producer providing the input to the decode
   * @return bitmap cache get to decode sequence
   */
  fun newBitmapCacheGetToDecodeSequence(
      inputProducer: Producer<EncodedImage>
  ): Producer<CloseableReference<CloseableImage>> {
    traceSection("ProducerSequenceFactory#newBitmapCacheGetToDecodeSequence") {
      val decodeProducer = producerFactory.newDecodeProducer(inputProducer)
      return newBitmapCacheGetToBitmapCacheSequence(decodeProducer)
    }
  }

  /**
   * encoded cache multiplex -> encoded cache -> (disk cache) -> (webp transcode)
   *
   * @param inputProducer producer providing the input to the transcode
   * @return encoded cache multiplex to webp transcode sequence
   */
  private fun newEncodedCacheMultiplexToTranscodeSequence(
      inputProducer: Producer<EncodedImage>
  ): Producer<EncodedImage> {
    var ip = inputProducer
    if (WebpSupportStatus.sIsWebpSupportRequired &&
        (!webpSupportEnabled || WebpSupportStatus.sWebpBitmapFactory == null)) {
      ip = producerFactory.newWebpTranscodeProducer(ip)
    }
    if (diskCacheEnabled) {
      ip = newDiskCacheSequence(ip)
    }
    val encodedMemoryCacheProducer = producerFactory.newEncodedMemoryCacheProducer(ip)
    if (isDiskCacheProbingEnabled) {
      val probeProducer = producerFactory.newEncodedProbeProducer(encodedMemoryCacheProducer)
      return producerFactory.newEncodedCacheKeyMultiplexProducer(probeProducer)
    }
    return producerFactory.newEncodedCacheKeyMultiplexProducer(encodedMemoryCacheProducer)
  }

  private fun newDiskCacheSequence(inputProducer: Producer<EncodedImage>): Producer<EncodedImage> =
      traceSection("ProducerSequenceFactory#newDiskCacheSequence") {
        val cacheWriteProducer =
            if (partialImageCachingEnabled) {
              val partialDiskCacheProducer: Producer<EncodedImage> =
                  producerFactory.newPartialDiskCacheProducer(inputProducer)
              producerFactory.newDiskCacheWriteProducer(partialDiskCacheProducer)
            } else {
              producerFactory.newDiskCacheWriteProducer(inputProducer)
            }
        return producerFactory.newDiskCacheReadProducer(cacheWriteProducer)
      }

  /**
   * Bitmap cache get -> thread hand off -> multiplex -> bitmap cache
   *
   * @param inputProducer producer providing the input to the bitmap cache
   * @return bitmap cache get to bitmap cache sequence
   */
  private fun newBitmapCacheGetToBitmapCacheSequence(
      inputProducer: Producer<CloseableReference<CloseableImage>>
  ): Producer<CloseableReference<CloseableImage>> {
    val bitmapMemoryCacheProducer = producerFactory.newBitmapMemoryCacheProducer(inputProducer)
    val bitmapKeyMultiplexProducer =
        producerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer)
    val threadHandoffProducer =
        producerFactory.newBackgroundThreadHandoffProducer(
            bitmapKeyMultiplexProducer, threadHandoffProducerQueue)
    if (isEncodedMemoryCacheProbingEnabled || isDiskCacheProbingEnabled) {
      val bitmapMemoryCacheGetProducer =
          producerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer)
      return producerFactory.newBitmapProbeProducer(bitmapMemoryCacheGetProducer)
    }
    return producerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer)
  }

  /**
   * Branch on separate images -> thumbnail resize and rotate -> thumbnail producers as provided ->
   * local image resize and rotate -> add meta data producer
   *
   * @param inputProducer producer providing the input to add meta data producer
   * @param thumbnailProducers the thumbnail producers from which to request the image before
   *   falling back to the full image producer sequence
   * @return local transformations sequence
   */
  private fun newLocalTransformationsSequence(
      inputProducer: Producer<EncodedImage>,
      thumbnailProducers: Array<ThumbnailProducer<EncodedImage>>
  ): Producer<EncodedImage> {
    var localImageProducer: Producer<EncodedImage> =
        ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer)
    localImageProducer =
        producerFactory.newResizeAndRotateProducer(localImageProducer, true, imageTranscoderFactory)
    val localImageThrottlingProducer = producerFactory.newThrottlingProducer(localImageProducer)
    return ProducerFactory.newBranchOnSeparateImagesProducer(
        newLocalThumbnailProducer(thumbnailProducers), localImageThrottlingProducer)
  }

  private fun newLocalThumbnailProducer(
      thumbnailProducers: Array<ThumbnailProducer<EncodedImage>>
  ): Producer<EncodedImage> {
    val thumbnailBranchProducer = producerFactory.newThumbnailBranchProducer(thumbnailProducers)
    return producerFactory.newResizeAndRotateProducer(
        thumbnailBranchProducer, true, imageTranscoderFactory)
  }

  /** post-processor producer -> copy producer -> inputProducer */
  @Synchronized
  private fun getPostprocessorSequence(
      inputProducer: Producer<CloseableReference<CloseableImage>>
  ): Producer<CloseableReference<CloseableImage>> {
    var result = postprocessorSequences[inputProducer]
    if (result == null) {
      val postprocessorProducer = producerFactory.newPostprocessorProducer(inputProducer)
      result = producerFactory.newPostprocessorBitmapMemoryCacheProducer(postprocessorProducer)
      postprocessorSequences[inputProducer] = result
    }
    return result
  }

  /** swallow result producer -> inputProducer */
  @Synchronized
  private fun getDecodedImagePrefetchSequence(
      inputProducer: Producer<CloseableReference<CloseableImage>>
  ): Producer<Void?> {
    var result = closeableImagePrefetchSequences[inputProducer]
    if (result == null) {
      result = producerFactory.newSwallowResultProducer(inputProducer)
      closeableImagePrefetchSequences[inputProducer] = result
    }
    return result
  }

  /** bitmap prepare producer -> inputProducer */
  @Synchronized
  private fun getBitmapPrepareSequence(
      inputProducer: Producer<CloseableReference<CloseableImage>>
  ): Producer<CloseableReference<CloseableImage>> {
    var bitmapPrepareProducer = bitmapPrepareSequences[inputProducer]
    if (bitmapPrepareProducer == null) {
      bitmapPrepareProducer = producerFactory.newBitmapPrepareProducer(inputProducer)
      bitmapPrepareSequences[inputProducer] = bitmapPrepareProducer
    }
    return bitmapPrepareProducer
  }

  @Synchronized
  private fun getDelaySequence(
      inputProducer: Producer<CloseableReference<CloseableImage>>
  ): Producer<CloseableReference<CloseableImage>> {
    return producerFactory.newDelayProducer(inputProducer)
  }

  companion object {
    private fun validateEncodedImageRequest(imageRequest: ImageRequest) {
      Preconditions.checkArgument(
          imageRequest.lowestPermittedRequestLevel.value <=
              ImageRequest.RequestLevel.ENCODED_MEMORY_CACHE.value)
    }

    private fun getShortenedUriString(uri: Uri): String {
      val uriString = uri.toString()
      return if (uriString.length > 30) uriString.substring(0, 30) + "..." else uriString
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.datasource

import com.facebook.common.internal.Preconditions
import com.facebook.datasource.AbstractDataSource
import com.facebook.imagepipeline.listener.RequestListener2
import com.facebook.imagepipeline.producers.BaseConsumer
import com.facebook.imagepipeline.producers.Consumer
import com.facebook.imagepipeline.producers.Producer
import com.facebook.imagepipeline.producers.ProducerContext
import com.facebook.imagepipeline.producers.SettableProducerContext
import com.facebook.imagepipeline.request.HasImageRequest
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.systrace.FrescoSystrace.traceSection
import javax.annotation.concurrent.ThreadSafe

/**
 * DataSource<T> backed by a Producer<T>
 *
 * @param <T> </T></T></T>
 */
@ThreadSafe
abstract class AbstractProducerToDataSourceAdapter<T>
protected constructor(
    producer: Producer<T>,
    val settableProducerContext: SettableProducerContext,
    val requestListener: RequestListener2
) : AbstractDataSource<T>(), HasImageRequest {

  private fun createConsumer(): Consumer<T> {
    return object : BaseConsumer<T>() {
      override fun onNewResultImpl(newResult: T?, @Consumer.Status status: Int) {
        this@AbstractProducerToDataSourceAdapter.onNewResultImpl(
            newResult, status, settableProducerContext)
      }

      override fun onFailureImpl(throwable: Throwable) {
        this@AbstractProducerToDataSourceAdapter.onFailureImpl(throwable)
      }

      override fun onCancellationImpl() {
        this@AbstractProducerToDataSourceAdapter.onCancellationImpl()
      }

      override fun onProgressUpdateImpl(progress: Float) {
        setProgress(progress)
      }
    }
  }

  protected open fun onNewResultImpl(result: T?, status: Int, producerContext: ProducerContext) {
    val isLast = BaseConsumer.isLast(status)
    if (super.setResult(result, isLast, getExtras(producerContext))) {
      if (isLast) {
        requestListener.onRequestSuccess(settableProducerContext)
      }
    }
  }

  protected fun getExtras(producerContext: ProducerContext): Map<String, Any?> =
      producerContext.getExtras()

  private fun onFailureImpl(throwable: Throwable) {
    if (super.setFailure(throwable, getExtras(settableProducerContext))) {
      requestListener.onRequestFailure(settableProducerContext, throwable)
    }
  }

  @Synchronized
  private fun onCancellationImpl() {
    Preconditions.checkState(isClosed)
  }

  override val imageRequest: ImageRequest?
    get() = settableProducerContext.imageRequest

  override fun close(): Boolean {
    if (!super.close()) {
      return false
    }
    if (!super.isFinished()) {
      requestListener.onRequestCancellation(settableProducerContext)
      settableProducerContext.cancel()
    }
    return true
  }

  init {
    traceSection("AbstractProducerToDataSourceAdapter()") {
      extras = settableProducerContext.getExtras()
      traceSection("AbstractProducerToDataSourceAdapter()->onRequestStart") {
        requestListener.onRequestStart(settableProducerContext)
      }
      traceSection("AbstractProducerToDataSourceAdapter()->produceResult") {
        producer.produceResults(createConsumer(), settableProducerContext)
      }
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/** DataSource implementations to use unto the image pipeline controller implementations. */
package com.facebook.imagepipeline.datasource


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.datasource

import com.facebook.datasource.DataSource
import com.facebook.imagepipeline.listener.RequestListener2
import com.facebook.imagepipeline.producers.Producer
import com.facebook.imagepipeline.producers.SettableProducerContext
import javax.annotation.concurrent.ThreadSafe

/**
 * DataSource<T> backed by a Producer<T>
 *
 * @param <T> </T></T></T>
 */
@ThreadSafe
class ProducerToDataSourceAdapter<T>
private constructor(
    producer: Producer<T>,
    settableProducerContext: SettableProducerContext,
    listener: RequestListener2
) : AbstractProducerToDataSourceAdapter<T>(producer, settableProducerContext, listener) {
  companion object {
    @JvmStatic
    fun <T> create(
        producer: Producer<T>,
        settableProducerContext: SettableProducerContext,
        listener: RequestListener2
    ): DataSource<T> = ProducerToDataSourceAdapter(producer, settableProducerContext, listener)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.datasource

import com.facebook.common.references.CloseableReference
import com.facebook.datasource.AbstractDataSource
import com.facebook.datasource.DataSource
import javax.annotation.concurrent.ThreadSafe

/**
 * A [DataSource] whose result may be set by a [set(CloseableReference<T>)] or
 * [setException(Throwable)] call. It may also be closed.
 *
 * This data source has no intermediate results - calling [set(CloseableReference<T>)] means that
 * the data source is finished. </T></T>
 */
@ThreadSafe
class SettableDataSource<T> private constructor() : AbstractDataSource<CloseableReference<T>?>() {

  /**
   * Sets the value of this data source.
   *
   * This method will return `true` if the value was successfully set, or `false` if the data source
   * has already been set, failed or closed.
   *
   * Passed CloseableReference is cloned, caller of this method still owns passed reference after
   * the method returns.
   *
   * @param valueRef closeable reference to the value the data source should hold.
   * @return true if the value was successfully set.
   */
  fun set(valueRef: CloseableReference<T>?): Boolean {
    val clonedRef = CloseableReference.cloneOrNull(valueRef)
    return super.setResult(clonedRef, /* isLast */ true, null)
  }

  /**
   * Sets the data source to having failed with the given exception.
   *
   * This method will return `true` if the exception was successfully set, or `false` if the data
   * source has already been set, failed or closed.
   *
   * @param throwable the exception the data source should hold.
   * @return true if the exception was successfully set.
   */
  fun setException(throwable: Throwable): Boolean = super.setFailure(throwable)

  /**
   * Sets the progress.
   *
   * @param progress the progress in range [0, 1] to be set.
   * @return true if the progress was successfully set.
   */
  public override fun setProgress(progress: Float): Boolean = super.setProgress(progress)

  /**
   * Gets the result if any, null otherwise.
   *
   * Value will be cloned and it's the caller's responsibility to close the returned value.
   */
  override fun getResult(): CloseableReference<T>? =
      CloseableReference.cloneOrNull(super.getResult())

  override fun closeResult(result: CloseableReference<T>?) {
    CloseableReference.closeSafely(result)
  }

  companion object {
    /** Creates a new `SettableDataSource` */
    @JvmStatic fun <V> create(): SettableDataSource<V> = SettableDataSource()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.debug

import com.facebook.common.references.SharedReference

/**
 * Tracker for leaks that occur when a [com.facebook.common.references.CloseableReference] is not
 * closed.
 */
interface CloseableReferenceLeakTracker {

  interface Listener {
    fun onCloseableReferenceLeak(reference: SharedReference<Any?>, stacktrace: Throwable?)
  }

  fun trackCloseableReferenceLeak(reference: SharedReference<Any?>, stacktrace: Throwable?)

  fun setListener(listener: Listener?)

  /** Indicate whether or not a listener is set. */
  val isSet: Boolean
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.debug

import com.facebook.common.references.SharedReference

class NoOpCloseableReferenceLeakTracker : CloseableReferenceLeakTracker {

  override fun trackCloseableReferenceLeak(
      reference: SharedReference<Any?>,
      stacktrace: Throwable?
  ) = Unit

  override fun setListener(listener: CloseableReferenceLeakTracker.Listener?) = Unit

  override val isSet: Boolean
    get() = false
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.decoder

import com.facebook.imagepipeline.image.EncodedImage

class DecodeException : RuntimeException {

  val encodedImage: EncodedImage

  constructor(message: String?, encodedImage: EncodedImage) : super(message) {
    this.encodedImage = encodedImage
  }

  constructor(message: String?, t: Throwable?, encodedImage: EncodedImage) : super(message, t) {
    this.encodedImage = encodedImage
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/** Decoder abstraction and some implementations for the main image formats. */
package com.facebook.imagepipeline.decoder


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.filter

import android.graphics.Bitmap
import com.facebook.common.internal.Preconditions
import com.facebook.imageutils.BitmapUtil

/**
 * Modified midpoint circle algorithm. Clients that look for better performances should use the
 * native implementation of this algorithm in NativeRoundingFilter.
 */
object InPlaceRoundFilter {

  /**
   * An implementation for rounding a given bitmap to a circular shape. The underlying
   * implementation uses a modified midpoint circle algorithm but instead of drawing a circle, it
   * clears all pixels starting from the circle all the way to the bitmap edges.
   *
   * @param bitmap The input [Bitmap]
   */
  @JvmStatic
  fun roundBitmapInPlace(bitmap: Bitmap) {
    Preconditions.checkNotNull(bitmap)
    val w = bitmap.width
    val h = bitmap.height
    val radius = Math.min(w, h) / 2
    val centerX = w / 2
    val centerY = h / 2
    // Nothing to do if the radius is equal to 0.
    if (radius == 0) {
      return
    }
    Preconditions.checkArgument(radius >= 1)
    Preconditions.checkArgument(w > 0 && w <= BitmapUtil.MAX_BITMAP_SIZE)
    Preconditions.checkArgument(h > 0 && h <= BitmapUtil.MAX_BITMAP_SIZE)
    Preconditions.checkArgument(centerX > 0 && centerX < w)
    Preconditions.checkArgument(centerY > 0 && centerY < h)
    val pixels = IntArray(w * h)
    bitmap.getPixels(pixels, 0, w, 0, 0, w, h)
    var x = radius - 1
    var y = 0
    val maxX = centerX + x
    val maxY = centerY + x
    val minX = centerX - x
    val minY = centerY - x
    Preconditions.checkArgument(minX >= 0 && minY >= 0 && maxX < w && maxY < h)
    var dx = 1
    var dy = 1
    val rInc = -radius * 2
    val transparentColor = IntArray(w)
    var err = dx + rInc
    var cXpX: Int
    var cXmX: Int
    var cXpY: Int
    var cXmY: Int
    var cYpX: Int
    var cYmX: Int
    var cYpY: Int
    var cYmY: Int
    var offA: Int
    var offB: Int
    var offC: Int
    var offD: Int
    while (x >= y) {
      cXpX = centerX + x
      cXmX = centerX - x
      cXpY = centerX + y
      cXmY = centerX - y
      cYpX = centerY + x
      cYmX = centerY - x
      cYpY = centerY + y
      cYmY = centerY - y
      Preconditions.checkArgument(x >= 0 && cXpY < w && cXmY >= 0 && cYpY < h && cYmY >= 0)
      offA = w * cYpY
      offB = w * cYmY
      offC = w * cYpX
      offD = w * cYmX

      // Clear left
      System.arraycopy(transparentColor, 0, pixels, offA, cXmX)
      System.arraycopy(transparentColor, 0, pixels, offB, cXmX)
      System.arraycopy(transparentColor, 0, pixels, offC, cXmY)
      System.arraycopy(transparentColor, 0, pixels, offD, cXmY)

      // Clear right
      System.arraycopy(transparentColor, 0, pixels, offA + cXpX, w - cXpX)
      System.arraycopy(transparentColor, 0, pixels, offB + cXpX, w - cXpX)
      System.arraycopy(transparentColor, 0, pixels, offC + cXpY, w - cXpY)
      System.arraycopy(transparentColor, 0, pixels, offD + cXpY, w - cXpY)
      if (err <= 0) {
        y++
        dy += 2
        err += dy
      }
      if (err > 0) {
        x--
        dx += 2
        err += dx + rInc
      }
    }

    // Clear top / bottom if height > width
    for (i in centerY - radius downTo 0) {
      System.arraycopy(transparentColor, 0, pixels, i * w, w)
    }
    for (i in centerY + radius until h) {
      System.arraycopy(transparentColor, 0, pixels, i * w, w)
    }
    bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.filter

import android.graphics.Bitmap
import com.facebook.common.internal.Preconditions
import com.facebook.common.logging.FLog
import com.facebook.imageutils.BitmapUtil
import java.util.Locale

object IterativeBoxBlurFilter {

  private const val TAG = "IterativeBoxBlurFilter"

  /**
   * An in-place iterative box blur algorithm that runs faster than a traditional box blur.
   *
   * The individual box blurs are split up in vertical and horizontal direction. That allows us to
   * use a moving average implementation for blurring individual rows and columns.
   *
   * The runtime is: O(iterations * width * height) and therefore linear in the number of pixels
   *
   * The required memory is: 2 * radius * 256 * 4 Bytes + max(width, height) * 4 Bytes + width *
   * height * 4 Bytes (+constant)
   *
   * @param bitmap The [Bitmap] containing the image. The bitmap dimension need to be smaller than
   *   [BitmapUtil.MAX_BITMAP_SIZE]
   * @param iterations The number of iterations of the blurring algorithm > 0.
   * @param radius The radius of the blur with a supported range 0 < radius <= [ ]
   *   [RenderScriptBlurFilter.BLUR_MAX_RADIUS]
   */
  @JvmStatic
  fun boxBlurBitmapInPlace(bitmap: Bitmap, iterations: Int, radius: Int) {
    checkNotNull(bitmap)
    Preconditions.checkArgument(bitmap.isMutable)
    Preconditions.checkArgument(bitmap.height <= BitmapUtil.MAX_BITMAP_SIZE)
    Preconditions.checkArgument(bitmap.width <= BitmapUtil.MAX_BITMAP_SIZE)
    Preconditions.checkArgument(radius > 0 && radius <= RenderScriptBlurFilter.BLUR_MAX_RADIUS)
    Preconditions.checkArgument(iterations > 0)
    try {
      fastBoxBlur(bitmap, iterations, radius)
    } catch (oom: OutOfMemoryError) {
      FLog.e(
          TAG,
          String.format(
              null as Locale?,
              "OOM: %d iterations on %dx%d with %d radius",
              iterations,
              bitmap.width,
              bitmap.height,
              radius))
      throw oom
    }
  }

  private fun fastBoxBlur(bitmap: Bitmap, iterations: Int, radius: Int) {
    val w = bitmap.width
    val h = bitmap.height
    val pixels = IntArray(w * h)
    bitmap.getPixels(pixels, 0, w, 0, 0, w, h)

    // The information written to an output pixels `x` are from `[x-radius, x+radius]` (inclusive)
    val diameter = radius + 1 + radius
    // Pre-compute division table: speed-up by factor 5(!)
    val div = IntArray(256 * diameter)

    // The following lines will fill-up at least the first `255 * diameter` entries with the mapping
    // `div[x] = (x + r) / d` (i.e. division of x by d rounded to the nearest number).
    var ptr = radius + 1
    for (b in 1..255) {
      for (d in 0 until diameter) {
        div[ptr] = b
        ptr++
      }
    }
    val tempRowOrColumn = IntArray(Math.max(w, h))
    for (i in 0 until iterations) {
      // Blur rows one-by-one
      for (row in 0 until h) {
        internalHorizontalBlur(pixels, tempRowOrColumn, w, row, diameter, div)
        System.arraycopy(tempRowOrColumn, 0, pixels, row * w, w)
      }

      // Blur columns one-by-one
      for (col in 0 until w) {
        internalVerticalBlur(pixels, tempRowOrColumn, w, h, col, diameter, div)
        var pos = col
        for (row in 0 until h) {
          pixels[pos] = tempRowOrColumn[row]
          pos += w
        }
      }
    }
    bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
  }

  /**
   * Creates a blurred version of the given `row` of `pixel`. It uses a moving average algorithm
   * such that it reads every pixel of the row just once. The edge pixels are repeated to avoid
   * artifacts.
   *
   * Requires a pre-computed `div` table of size (255 * diameter) that maps x -> (x / diameter) (can
   * be rounded)
   */
  private fun internalHorizontalBlur(
      pixels: IntArray,
      outRow: IntArray,
      w: Int,
      row: Int,
      diameter: Int,
      div: IntArray
  ) {
    val firstInByte = w * row
    val lastInByte = w * (row + 1) - 1
    val radius = diameter shr 1
    var a = 0
    var r = 0
    var g = 0
    var b = 0
    var pixel: Int

    // Iterate over relative position to first pixel of row
    for (i in -radius until w + radius) {
      val ii = bound(firstInByte + i, firstInByte, lastInByte)
      pixel = pixels[ii]
      r += pixel shr 16 and 0xFF
      g += pixel shr 8 and 0xFF
      b += pixel and 0xFF
      a += pixel ushr 24
      if (i >= radius) {
        val outOffset = i - radius
        outRow[outOffset] = div[a] shl 24 or (div[r] shl 16) or (div[g] shl 8) or div[b]
        val j = i - (diameter - 1)
        val jj = bound(firstInByte + j, firstInByte, lastInByte)
        pixel = pixels[jj]
        r -= pixel shr 16 and 0xFF
        g -= pixel shr 8 and 0xFF
        b -= pixel and 0xFF
        a -= pixel ushr 24
      }
    }
  }

  /**
   * Creates a blurred version of the given `col` of `pixels`. It uses a moving average algorithm
   * such that it reads every pixel of the column just once. The edge pixels are repeated to avoid
   * artifacts.
   *
   * Requires a pre-computed `div` table of size (255 * diameter) that maps x -> (x / diameter) (can
   * be rounded)
   */
  private fun internalVerticalBlur(
      pixels: IntArray,
      outCol: IntArray,
      w: Int,
      h: Int,
      col: Int,
      diameter: Int,
      div: IntArray
  ) {
    val lastInByte = w * (h - 1) + col
    val radiusTimesW = (diameter shr 1) * w
    val diameterMinusOneTimesW = (diameter - 1) * w
    var a = 0
    var r = 0
    var g = 0
    var b = 0
    var pixel: Int
    var outColPos = 0

    // iterate over absolute positions in `pixelsIn`; `w` is the step width for moving down one row
    var i = col - radiusTimesW
    while (i <= lastInByte + radiusTimesW) {
      val ii = bound(i, col, lastInByte)
      pixel = pixels[ii]
      r += pixel shr 16 and 0xFF
      g += pixel shr 8 and 0xFF
      b += pixel and 0xFF
      a += pixel ushr 24
      val outPos = i - radiusTimesW
      if (outPos >= col) {
        outCol[outColPos] = div[a] shl 24 or (div[r] shl 16) or (div[g] shl 8) or div[b]
        outColPos++
        val j = i - diameterMinusOneTimesW
        val jj = bound(j, col, lastInByte)
        pixel = pixels[jj]
        r -= pixel shr 16 and 0xFF
        g -= pixel shr 8 and 0xFF
        b -= pixel and 0xFF
        a -= pixel ushr 24
      }
      i += w
    }
  }

  private fun bound(x: Int, l: Int, h: Int): Int {
    return if (x < l) l else if (x > h) h else x
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.filter

import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import androidx.annotation.RequiresApi
import com.facebook.common.internal.Preconditions

object RenderScriptBlurFilter {

  const val BLUR_MAX_RADIUS = 25

  /**
   * Not-in-place intrinsic Gaussian blur filter using [ScriptIntrinsicBlur] and [ ]. This require
   * an Android versions >= 4.2.
   *
   * @param dest The [Bitmap] where the blurred image is written to.
   * @param src The [Bitmap] containing the original image.
   * @param context The [Context] necessary to use [RenderScript]
   * @param radius The radius of the blur with a supported range 0 < radius <= [ ][.BLUR_MAX_RADIUS]
   */
  @JvmStatic
  @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
  fun blurBitmap(dest: Bitmap, src: Bitmap, context: Context, radius: Int) {
    Preconditions.checkNotNull(dest)
    Preconditions.checkNotNull(src)
    Preconditions.checkNotNull(context)
    Preconditions.checkArgument(radius > 0 && radius <= BLUR_MAX_RADIUS)
    var rs: RenderScript? = null
    try {
      rs = Preconditions.checkNotNull(RenderScript.create(context))

      // Create an Intrinsic Blur Script using the Renderscript
      val blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))

      // Create the input/output allocations with Renderscript and the src/dest bitmaps
      val allIn = Preconditions.checkNotNull(Allocation.createFromBitmap(rs, src))
      val allOut = Preconditions.checkNotNull(Allocation.createFromBitmap(rs, dest))

      // Set the radius of the blur
      blurScript.setRadius(radius.toFloat())
      blurScript.setInput(allIn)
      blurScript.forEach(allOut)
      allOut.copyTo(dest)
      blurScript.destroy()
      allIn.destroy()
      allOut.destroy()
    } finally {
      rs?.destroy()
    }
  }

  @JvmStatic
  fun canUseRenderScript(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.filter

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import com.facebook.common.internal.Preconditions

/**
 * Not-in-place rounding image algorithm using [Canvas] that requires an Android version >= 3.1. If
 * image quality is preferred above performance, this algorithm performs anti-aliasing and will
 * generate better looking images, otherwise clients that look for better performances should use
 * NativeRoundingFilter.
 */
object XferRoundFilter {
  @JvmStatic
  fun xferRoundBitmap(output: Bitmap, source: Bitmap, enableAntiAliasing: Boolean) {
    Preconditions.checkNotNull(source)
    Preconditions.checkNotNull(output)
    output.setHasAlpha(true)
    val circlePaint: Paint
    val xfermodePaint: Paint
    if (enableAntiAliasing) {
      circlePaint = Paint(Paint.ANTI_ALIAS_FLAG)
      xfermodePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    } else {
      circlePaint = Paint()
      xfermodePaint = Paint()
    }
    circlePaint.color = Color.BLACK
    xfermodePaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
    val xCenter = source.width / 2f
    val yCenter = source.height / 2f
    Canvas(output).apply {
      drawCircle(xCenter, yCenter, Math.min(xCenter, yCenter), circlePaint)
      drawBitmap(source, 0f, 0f, xfermodePaint)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.listener

import com.facebook.imagepipeline.request.ImageRequest

open class BaseRequestListener : RequestListener {

  override fun onRequestStart(
      request: ImageRequest,
      callerContext: Any,
      requestId: String,
      isPrefetch: Boolean
  ) = Unit

  override fun onRequestSuccess(request: ImageRequest, requestId: String, isPrefetch: Boolean) =
      Unit

  override fun onRequestFailure(
      request: ImageRequest,
      requestId: String,
      throwable: Throwable,
      isPrefetch: Boolean
  ) = Unit

  override fun onRequestCancellation(requestId: String) = Unit

  override fun onProducerStart(requestId: String, producerName: String) = Unit

  override fun onProducerEvent(requestId: String, producerName: String, eventName: String) = Unit

  override fun onProducerFinishWithSuccess(
      requestId: String,
      producerName: String,
      extraMap: Map<String, String>?
  ) = Unit

  override fun onProducerFinishWithFailure(
      requestId: String,
      producerName: String,
      t: Throwable,
      extraMap: Map<String, String>?
  ) = Unit

  override fun onProducerFinishWithCancellation(
      requestId: String,
      producerName: String,
      extraMap: Map<String, String>?
  ) = Unit

  override fun onUltimateProducerReached(
      requestId: String,
      producerName: String,
      successful: Boolean
  ) = Unit

  override fun requiresExtraMap(requestId: String): Boolean = false
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.listener

import com.facebook.imagepipeline.producers.ProducerContext

class BaseRequestListener2 : RequestListener2 {

  override fun onRequestStart(producerContext: ProducerContext) = Unit

  override fun onRequestSuccess(producerContext: ProducerContext) = Unit

  override fun onRequestFailure(producerContext: ProducerContext, throwable: Throwable?) = Unit

  override fun onRequestCancellation(producerContext: ProducerContext) = Unit

  override fun onProducerStart(producerContext: ProducerContext, producerName: String) = Unit

  override fun onProducerEvent(
      producerContext: ProducerContext,
      producerName: String,
      eventName: String
  ) = Unit

  override fun onProducerFinishWithSuccess(
      producerContext: ProducerContext,
      producerName: String,
      extraMap: Map<String, String>?
  ) = Unit

  override fun onProducerFinishWithFailure(
      producerContext: ProducerContext,
      producerName: String,
      t: Throwable?,
      extraMap: Map<String, String>?
  ) = Unit

  override fun onProducerFinishWithCancellation(
      producerContext: ProducerContext,
      producerName: String,
      extraMap: Map<String, String>?
  ) = Unit

  override fun onUltimateProducerReached(
      producerContext: ProducerContext,
      producerName: String,
      successful: Boolean
  ) = Unit

  override fun requiresExtraMap(producerContext: ProducerContext, producerName: String): Boolean =
      false
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.listener

import com.facebook.common.logging.FLog
import com.facebook.imagepipeline.producers.ProducerContext
import java.util.ArrayList
import java.util.Set

class ForwardingRequestListener2 : RequestListener2 {

  private var requestListeners: MutableList<RequestListener2>

  constructor(listenersToAdd: Set<RequestListener2?>) {
    requestListeners = ArrayList(listenersToAdd.size)
    listenersToAdd.filterNotNullTo(requestListeners)
  }

  constructor(vararg listenersToAdd: RequestListener2?) {
    requestListeners = ArrayList(listenersToAdd.size)
    listenersToAdd.filterNotNullTo(requestListeners)
  }

  fun addRequestListener(requestListener: RequestListener2) {
    requestListeners.add(requestListener)
  }

  private inline fun forEachListener(methodName: String, block: (RequestListener2) -> Unit) {
    requestListeners.forEach {
      try {
        block(it)
      } catch (exception: Exception) {
        // Don't punish the other listeners if we're given a bad one.
        FLog.e(TAG, "InternalListener exception in $methodName", exception)
      }
    }
  }

  override fun onRequestStart(producerContext: ProducerContext) {
    forEachListener("onRequestStart") { it.onRequestStart(producerContext) }
  }

  override fun onProducerStart(producerContext: ProducerContext, producerName: String) {
    forEachListener("onProducerStart") { it.onProducerStart(producerContext, producerName) }
  }

  override fun onProducerFinishWithSuccess(
      producerContext: ProducerContext?,
      producerName: String?,
      extraMap: MutableMap<String, String>?
  ) {
    forEachListener("onProducerFinishWithSuccess") {
      it.onProducerFinishWithSuccess(producerContext, producerName, extraMap)
    }
  }

  override fun onProducerFinishWithFailure(
      producerContext: ProducerContext?,
      producerName: String?,
      t: Throwable?,
      extraMap: MutableMap<String, String>?
  ) {
    forEachListener("onProducerFinishWithFailure") {
      it.onProducerFinishWithFailure(producerContext, producerName, t, extraMap)
    }
  }

  override fun onProducerFinishWithCancellation(
      producerContext: ProducerContext?,
      producerName: String?,
      extraMap: MutableMap<String, String>?
  ) {
    forEachListener("onProducerFinishWithCancellation") {
      it.onProducerFinishWithCancellation(producerContext, producerName, extraMap)
    }
  }

  override fun onProducerEvent(
      producerContext: ProducerContext,
      producerName: String,
      producerEventName: String
  ) {
    forEachListener("onIntermediateChunkStart") {
      it.onProducerEvent(producerContext, producerName, producerEventName)
    }
  }

  override fun onUltimateProducerReached(
      producerContext: ProducerContext,
      producerName: String,
      successful: Boolean
  ) {
    forEachListener("onProducerFinishWithSuccess") {
      it.onUltimateProducerReached(producerContext, producerName, successful)
    }
  }

  override fun onRequestSuccess(producerContext: ProducerContext) {
    forEachListener("onRequestSuccess") { it.onRequestSuccess(producerContext) }
  }

  override fun onRequestFailure(producerContext: ProducerContext, throwable: Throwable) {
    forEachListener("onRequestFailure") { it.onRequestFailure(producerContext, throwable) }
  }

  override fun onRequestCancellation(producerContext: ProducerContext) {
    forEachListener("onRequestCancellation") { it.onRequestCancellation(producerContext) }
  }

  override fun requiresExtraMap(producerContext: ProducerContext, producerName: String): Boolean =
      requestListeners.any { it.requiresExtraMap(producerContext, producerName) }

  companion object {
    private const val TAG = "ForwardingRequestListener2"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/** Listeners for the image pipeline. */
package com.facebook.imagepipeline.listener


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.listener

import android.os.SystemClock
import android.util.Pair
import com.facebook.common.logging.FLog
import com.facebook.imagepipeline.request.ImageRequest
import java.util.HashMap
import javax.annotation.concurrent.GuardedBy

/** Logging for [ImageRequest]s. */
class RequestLoggingListener : RequestListener {

  @GuardedBy("this")
  private val producerStartTimeMap: MutableMap<Pair<String, String>, Long> = HashMap()

  @GuardedBy("this") private val requestStartTimeMap: MutableMap<String, Long> = HashMap()

  @Synchronized
  override fun onRequestStart(
      request: ImageRequest,
      callerContextObject: Any,
      requestId: String,
      isPrefetch: Boolean
  ) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      FLog.v(
          TAG,
          "time %d: onRequestSubmit: {requestId: %s, callerContext: %s, isPrefetch: %b}",
          time,
          requestId,
          callerContextObject,
          isPrefetch)
      requestStartTimeMap[requestId] = time
    }
  }

  @Synchronized
  override fun onProducerStart(requestId: String, producerName: String) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val mapKey = Pair.create(requestId, producerName)
      val startTime = time
      producerStartTimeMap[mapKey] = startTime
      FLog.v(
          TAG,
          "time %d: onProducerStart: {requestId: %s, producer: %s}",
          startTime,
          requestId,
          producerName)
    }
  }

  @Synchronized
  override fun onProducerFinishWithSuccess(
      requestId: String,
      producerName: String,
      extraMap: Map<String, String>?
  ) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val mapKey = Pair.create(requestId, producerName)
      val startTime = producerStartTimeMap.remove(mapKey)
      val currentTime = time
      FLog.v(
          TAG,
          "time %d: onProducerFinishWithSuccess: {requestId: %s, producer: %s, elapsedTime: %d ms, extraMap: %s}",
          currentTime,
          requestId,
          producerName,
          getElapsedTime(startTime, currentTime),
          extraMap)
    }
  }

  @Synchronized
  override fun onProducerFinishWithFailure(
      requestId: String,
      producerName: String,
      throwable: Throwable,
      extraMap: Map<String, String>?
  ) {
    if (FLog.isLoggable(FLog.WARN)) {
      val mapKey = Pair.create(requestId, producerName)
      val startTime = producerStartTimeMap.remove(mapKey)
      val currentTime = time
      FLog.w(
          TAG,
          throwable,
          "time %d: onProducerFinishWithFailure: {requestId: %s, stage: %s, elapsedTime: %d ms, extraMap: %s, throwable: %s}",
          currentTime,
          requestId,
          producerName,
          getElapsedTime(startTime, currentTime),
          extraMap,
          throwable.toString())
    }
  }

  @Synchronized
  override fun onProducerFinishWithCancellation(
      requestId: String,
      producerName: String,
      extraMap: Map<String, String>?
  ) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val mapKey = Pair.create(requestId, producerName)
      val startTime = producerStartTimeMap.remove(mapKey)
      val currentTime = time
      FLog.v(
          TAG,
          "time %d: onProducerFinishWithCancellation: {requestId: %s, stage: %s, elapsedTime: %d ms, extraMap: %s}",
          currentTime,
          requestId,
          producerName,
          getElapsedTime(startTime, currentTime),
          extraMap)
    }
  }

  @Synchronized
  override fun onProducerEvent(requestId: String, producerName: String, producerEventName: String) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val mapKey = Pair.create(requestId, producerName)
      val startTime = producerStartTimeMap[mapKey]
      val currentTime = time
      FLog.v(
          TAG,
          "time %d: onProducerEvent: {requestId: %s, stage: %s, eventName: %s; elapsedTime: %d ms}",
          time,
          requestId,
          producerName,
          producerEventName,
          getElapsedTime(startTime, currentTime))
    }
  }

  @Synchronized
  override fun onUltimateProducerReached(
      requestId: String,
      producerName: String,
      successful: Boolean
  ) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val mapKey = Pair.create(requestId, producerName)
      val startTime = producerStartTimeMap.remove(mapKey)
      val currentTime = time
      FLog.v(
          TAG,
          "time %d: onUltimateProducerReached: {requestId: %s, producer: %s, elapsedTime: %d ms, success: %b}",
          currentTime,
          requestId,
          producerName,
          getElapsedTime(startTime, currentTime),
          successful)
    }
  }

  @Synchronized
  override fun onRequestSuccess(request: ImageRequest, requestId: String, isPrefetch: Boolean) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val startTime = requestStartTimeMap.remove(requestId)
      val currentTime = time
      FLog.v(
          TAG,
          "time %d: onRequestSuccess: {requestId: %s, elapsedTime: %d ms}",
          currentTime,
          requestId,
          getElapsedTime(startTime, currentTime))
    }
  }

  @Synchronized
  override fun onRequestFailure(
      request: ImageRequest,
      requestId: String,
      throwable: Throwable,
      isPrefetch: Boolean
  ) {
    if (FLog.isLoggable(FLog.WARN)) {
      val startTime = requestStartTimeMap.remove(requestId)
      val currentTime = time
      FLog.w(
          TAG,
          "time %d: onRequestFailure: {requestId: %s, elapsedTime: %d ms, throwable: %s}",
          currentTime,
          requestId,
          getElapsedTime(startTime, currentTime),
          throwable.toString())
    }
  }

  @Synchronized
  override fun onRequestCancellation(requestId: String) {
    if (FLog.isLoggable(FLog.VERBOSE)) {
      val startTime = requestStartTimeMap.remove(requestId)
      val currentTime = time
      FLog.v(
          TAG,
          "time %d: onRequestCancellation: {requestId: %s, elapsedTime: %d ms}",
          currentTime,
          requestId,
          getElapsedTime(startTime, currentTime))
    }
  }

  override fun requiresExtraMap(id: String): Boolean = FLog.isLoggable(FLog.VERBOSE)

  companion object {
    private const val TAG = "RequestLoggingListener"

    private fun getElapsedTime(startTime: Long?, endTime: Long): Long =
        if (startTime != null) {
          endTime - startTime
        } else {
          -1
        }

    private val time: Long
      get() = SystemClock.uptimeMillis()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

class BitmapCounterConfig(val maxBitmapCount: Int = DEFAULT_MAX_BITMAP_COUNT) {

  companion object {
    const val DEFAULT_MAX_BITMAP_COUNT = 384
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import com.facebook.infer.annotation.ThreadSafe
import kotlin.jvm.JvmField

object BitmapCounterProvider {

  private const val KB: Long = 1_024
  private const val MB = 1_024 * KB

  /**
   * Our Bitmaps live in ashmem, meaning that they are pinned in Android's shared native memory.
   *
   * Therefore, we are not constrained by the max heap size of the dalvik heap, but we want to make
   * sure we don't use too much memory on low end devices, so that we don't force other background
   * process to be killed.
   */
  @JvmField val MAX_BITMAP_TOTAL_SIZE = maxSizeHardCap
  private var maxBitmapCount = BitmapCounterConfig.DEFAULT_MAX_BITMAP_COUNT

  @Volatile private var bitmapCounter: BitmapCounter? = null
  private val maxSizeHardCap: Int
    get() {
      val maxMemory = Math.min(Runtime.getRuntime().maxMemory(), Int.MAX_VALUE.toLong()).toInt()
      return if (maxMemory > 16 * MB) {
        maxMemory / 4 * 3
      } else {
        maxMemory / 2
      }
    }

  @JvmStatic
  fun initialize(bitmapCounterConfig: BitmapCounterConfig) {
    check(bitmapCounter == null) {
      "BitmapCounter has already been created! `BitmapCounterProvider.initialize(...)` should only be called before `BitmapCounterProvider.get()` or not at all!"
    }
    maxBitmapCount = bitmapCounterConfig.maxBitmapCount
  }

  @JvmStatic
  @ThreadSafe
  fun get(): BitmapCounter {
    if (bitmapCounter == null) {
      synchronized(BitmapCounterProvider::class.java) {
        if (bitmapCounter == null) {
          bitmapCounter = BitmapCounter(maxBitmapCount, MAX_BITMAP_TOTAL_SIZE)
        }
      }
    }
    return bitmapCounter!!
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.graphics.Bitmap
import com.facebook.common.memory.Pool

interface BitmapPool : Pool<Bitmap>


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.graphics.Bitmap
import android.graphics.Color
import com.facebook.common.logging.FLog
import com.facebook.imageutils.BitmapUtil

open class BitmapPoolBackend : LruBucketsPoolBackend<Bitmap>() {

  override fun put(bitmap: Bitmap) {
    if (isReusable(bitmap)) {
      super.put(bitmap)
    }
  }

  override fun get(size: Int): Bitmap? {
    val bitmap = super.get(size)
    if (bitmap != null && isReusable(bitmap)) {
      bitmap.eraseColor(Color.TRANSPARENT)
      return bitmap
    }
    return null
  }

  override fun getSize(bitmap: Bitmap): Int = BitmapUtil.getSizeInBytes(bitmap)

  protected fun isReusable(bitmap: Bitmap?): Boolean {
    if (bitmap == null) {
      return false
    }
    if (bitmap.isRecycled) {
      FLog.wtf(TAG, "Cannot reuse a recycled bitmap: %s", bitmap)
      return false
    }
    if (!bitmap.isMutable) {
      FLog.wtf(TAG, "Cannot reuse an immutable bitmap: %s", bitmap)
      return false
    }
    return true
  }

  companion object {
    private const val TAG = "BitmapPoolBackend"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

annotation class BitmapPoolType {
  companion object {
    const val LEGACY = "legacy"
    const val LEGACY_DEFAULT_PARAMS = "legacy_default_params"
    const val DUMMY = "dummy"
    const val DUMMY_WITH_TRACKING = "dummy_with_tracking"
    const val EXPERIMENTAL = "experimental"
    const val DEFAULT = LEGACY
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.util.SparseIntArray
import com.facebook.common.util.ByteConstants

/** Provides pool parameters for [BitmapPool] */
object DefaultBitmapPoolParams {

  /** We are not reusing Bitmaps and want to free them as soon as possible. */
  private const val MAX_SIZE_SOFT_CAP = 0

  /**
   * Our Bitmaps live in ashmem, meaning that they are pinned in androids' shared native memory.
   * Therefore, we are not constrained by the max heap size of the dalvik heap, but we want to make
   * sure we don't use too much memory on low end devices, so that we don't force other background
   * process to be evicted.
   */
  private val maxSizeHardCap: Int
    get() {
      val maxMemory = Math.min(Runtime.getRuntime().maxMemory(), Int.MAX_VALUE.toLong()).toInt()
      return if (maxMemory > 16 * ByteConstants.MB) {
        maxMemory / 4 * 3
      } else {
        maxMemory / 2
      }
    }

  /** This will cause all get/release calls to behave like alloc/free calls i.e. no pooling. */
  private val DEFAULT_BUCKETS = SparseIntArray(0)

  @JvmStatic fun get(): PoolParams = PoolParams(MAX_SIZE_SOFT_CAP, maxSizeHardCap, DEFAULT_BUCKETS)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.util.SparseIntArray
import com.facebook.common.util.ByteConstants

/** Provides pool parameters ([PoolParams]) for common [ByteArrayPool] */
object DefaultByteArrayPoolParams {

  private const val DEFAULT_IO_BUFFER_SIZE = 16 * ByteConstants.KB

  /*
   * There are up to 5 simultaneous IO operations in new pipeline performed by:
   * - 3 image-fetch threads
   * - 2 image-cache threads
   * We should be able to satisfy these requirements without any allocations
   */
  private const val DEFAULT_BUCKET_SIZE = 5
  private const val MAX_SIZE_SOFT_CAP = 5 * DEFAULT_IO_BUFFER_SIZE

  /** We don't need hard cap here. */
  private const val MAX_SIZE_HARD_CAP = 1 * ByteConstants.MB

  /** Get default [PoolParams]. */
  @JvmStatic
  fun get(): PoolParams {
    // This pool supports only one bucket size: DEFAULT_IO_BUFFER_SIZE
    val defaultBuckets = SparseIntArray()
    defaultBuckets.put(DEFAULT_IO_BUFFER_SIZE, DEFAULT_BUCKET_SIZE)
    return PoolParams(MAX_SIZE_SOFT_CAP, MAX_SIZE_HARD_CAP, defaultBuckets)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.util.SparseIntArray
import com.facebook.common.util.ByteConstants

/** Provides pool parameters ([PoolParams]) for [SharedByteArray] */
object DefaultFlexByteArrayPoolParams {

  // the default max buffer size we'll use
  const val DEFAULT_MAX_BYTE_ARRAY_SIZE = 4 * ByteConstants.MB

  // the min buffer size we'll use
  private const val DEFAULT_MIN_BYTE_ARRAY_SIZE = 128 * ByteConstants.KB

  // the maximum number of threads permitted to touch this pool
  val DEFAULT_MAX_NUM_THREADS = Runtime.getRuntime().availableProcessors()

  @JvmStatic
  fun generateBuckets(min: Int, max: Int, numThreads: Int): SparseIntArray {
    val buckets = SparseIntArray()
    var i = min
    while (i <= max) {
      buckets.put(i, numThreads)
      i *= 2
    }
    return buckets
  }

  @JvmStatic
  fun get(): PoolParams =
      PoolParams(
          /* maxSizeSoftCap */
          DEFAULT_MAX_BYTE_ARRAY_SIZE, /* maxSizeHardCap */
          DEFAULT_MAX_NUM_THREADS * DEFAULT_MAX_BYTE_ARRAY_SIZE, /* bucketSizes */
          generateBuckets(
              DEFAULT_MIN_BYTE_ARRAY_SIZE,
              DEFAULT_MAX_BYTE_ARRAY_SIZE,
              DEFAULT_MAX_NUM_THREADS), /* minBucketSize */
          DEFAULT_MIN_BYTE_ARRAY_SIZE, /* maxBucketSize */
          DEFAULT_MAX_BYTE_ARRAY_SIZE, /* maxNumThreads */
          DEFAULT_MAX_NUM_THREADS)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.util.SparseIntArray
import com.facebook.common.util.ByteConstants

/** Provides pool parameters ([PoolParams]) for [NativeMemoryChunkPool] */
object DefaultNativeMemoryChunkPoolParams {

  /**
   * Length of 'small' sized buckets. Bucket lengths for these buckets are larger because they're
   * smaller in size
   */
  private const val SMALL_BUCKET_LENGTH = 5

  /** Bucket lengths for 'large' (> 256KB) buckets */
  private const val LARGE_BUCKET_LENGTH = 2

  @JvmStatic
  fun get(): PoolParams {
    val DEFAULT_BUCKETS = SparseIntArray()
    DEFAULT_BUCKETS.put(1 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(2 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(4 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(8 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(16 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(32 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(64 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(128 * ByteConstants.KB, SMALL_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(256 * ByteConstants.KB, LARGE_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(512 * ByteConstants.KB, LARGE_BUCKET_LENGTH)
    DEFAULT_BUCKETS.put(1_024 * ByteConstants.KB, LARGE_BUCKET_LENGTH)
    return PoolParams(maxSizeSoftCap, maxSizeHardCap, DEFAULT_BUCKETS)
  }

  /**
   * [NativeMemoryChunkPool] manages memory on the native heap, so we don't need as strict caps as
   * we would if we were on the Dalvik heap. However, since native memory OOMs are significantly
   * more problematic than Dalvik OOMs, we would like to stay conservative.
   */
  private val maxSizeSoftCap: Int
    get() {
      val maxMemory = Math.min(Runtime.getRuntime().maxMemory(), Int.MAX_VALUE.toLong()).toInt()
      return if (maxMemory < 16 * ByteConstants.MB) {
        3 * ByteConstants.MB
      } else if (maxMemory < 32 * ByteConstants.MB) {
        6 * ByteConstants.MB
      } else {
        12 * ByteConstants.MB
      }
    }

  /**
   * We need a smaller cap for devices with less then 16 MB so that we don't run the risk of
   * evicting other processes from the native heap.
   */
  private val maxSizeHardCap: Int
    get() {
      val maxMemory = Math.min(Runtime.getRuntime().maxMemory(), Int.MAX_VALUE.toLong()).toInt()
      return if (maxMemory < 16 * ByteConstants.MB) {
        maxMemory / 2
      } else {
        maxMemory / 4 * 3
      }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.graphics.Bitmap
import com.facebook.common.memory.MemoryTrimType
import com.facebook.imageutils.BitmapUtil

class DummyBitmapPool : BitmapPool {

  override fun trim(trimType: MemoryTrimType) {
    // nop
  }

  override fun get(size: Int): Bitmap =
      Bitmap.createBitmap(
          1,
          Math.ceil(size / BitmapUtil.RGB_565_BYTES_PER_PIXEL.toDouble()).toInt(),
          Bitmap.Config.RGB_565)

  override fun release(value: Bitmap) {
    checkNotNull(value)
    value.recycle()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import android.graphics.Bitmap
import com.facebook.common.internal.Sets
import com.facebook.common.memory.MemoryTrimType
import com.facebook.imageutils.BitmapUtil

class DummyTrackingInUseBitmapPool : BitmapPool {

  /** An Identity hash-set to keep track of values by reference equality */
  private val inUseValues: MutableSet<Bitmap> = Sets.newIdentityHashSet()

  override fun trim(trimType: MemoryTrimType) {
    // nop
  }

  override fun get(size: Int): Bitmap {
    val result =
        Bitmap.createBitmap(
            1,
            Math.ceil(size / BitmapUtil.RGB_565_BYTES_PER_PIXEL.toDouble()).toInt(),
            Bitmap.Config.RGB_565)
    inUseValues.add(result)
    return result
  }

  override fun release(value: Bitmap) {
    checkNotNull(value)
    inUseValues.remove(value)
    value.recycle()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import com.facebook.common.memory.ByteArrayPool
import com.facebook.common.memory.MemoryTrimmableRegistry
import javax.annotation.concurrent.ThreadSafe

/**
 * A pool of byte arrays. The pool manages a number of byte arrays of a predefined set of sizes.
 * This set of sizes is typically, but not required to be, based on powers of 2. The pool supports a
 * get/release paradigm. On a get request, the pool attempts to find an existing byte array whose
 * size is at least as big as the requested size. On a release request, the pool adds the byte array
 * to the appropriate bucket. This byte array can then be used for a subsequent get request.
 */
@ThreadSafe
open class GenericByteArrayPool(
    memoryTrimmableRegistry: MemoryTrimmableRegistry,
    poolParams: PoolParams,
    poolStatsTracker: PoolStatsTracker
) : BasePool<ByteArray?>(memoryTrimmableRegistry, poolParams, poolStatsTracker), ByteArrayPool {

  private val bucketSizes: IntArray

  /**
   * Gets the smallest buffer size supported by the pool
   *
   * @return the smallest buffer size supported by the pool
   */
  val minBufferSize: Int
    get() = if (bucketSizes.size > 0) bucketSizes[0] else 0

  /**
   * Creates a new instance of the GenericByteArrayPool class
   *
   * @param memoryTrimmableRegistry the memory manager to register with
   * @param poolParams provider for pool parameters
   * @param poolStatsTracker
   */
  init {
    val bucketSizes = poolParams.bucketSizes
    if (bucketSizes != null) {
      this.bucketSizes = IntArray(bucketSizes.size())
      for (i in 0 until bucketSizes.size()) {
        this.bucketSizes[i] = bucketSizes.keyAt(i)
      }
    } else {
      this.bucketSizes = IntArray(0)
    }
    initialize()
  }

  /**
   * Allocate a buffer greater than or equal to the specified size
   *
   * @param bucketedSize size of the buffer requested
   * @return a byte array of the specified or larger size. Null if the size is invalid
   */
  override fun alloc(bucketedSize: Int): ByteArray = ByteArray(bucketedSize)

  /**
   * Frees the 'value'
   *
   * @param value the value to free
   */
  override fun free(value: ByteArray) {
    checkNotNull(value)
    // do nothing. Let the GC take care of this
  }

  /**
   * Gets the size in bytes for the given 'bucketed' size
   *
   * @param bucketedSize the bucketed size
   * @return size in bytes
   */
  override fun getSizeInBytes(bucketedSize: Int): Int = bucketedSize

  /**
   * Get the 'bucketed' size for the given request size. The 'bucketed' size is a size that is the
   * same or larger than the request size. We walk through our list of pre-defined bucket sizes, and
   * use that to determine the smallest bucket size that is larger than the requested size. If no
   * such 'bucketedSize' is found, then we simply return "requestSize"
   *
   * @param requestSize the logical request size
   * @return the bucketed size
   * @throws InvalidSizeException, if the requested size was invalid
   */
  override fun getBucketedSize(requestSize: Int): Int {
    if (requestSize <= 0) {
      throw InvalidSizeException(requestSize)
    }

    // find the smallest bucketed size that is larger than the requested size
    for (bucketedSize in bucketSizes) {
      if (bucketedSize >= requestSize) {
        return bucketedSize
      }
    }

    // requested size doesn't match our existing buckets - just return the requested size
    // this will eventually translate into a plain alloc/free paradigm
    return requestSize
  }

  /**
   * Gets the bucketed size of the value
   *
   * @param value the value
   * @return just the length of the value
   */
  override fun getBucketedSizeForValue(value: ByteArray): Int {
    checkNotNull(value)
    return value.size
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import java.nio.ByteBuffer

interface MemoryChunk {

  /**
   * This has to be called before we get rid of this object in order to release underlying memory
   */
  fun close()

  /**
   * Check if this chunk is already closed
   *
   * @return true, if this chunk has already been closed
   */
  fun isClosed(): Boolean

  /** Get the size of this memory chunk. Ignores if this chunk has been closed */
  val size: Int

  /**
   * Copy bytes from byte array to buffer.
   *
   * @param memoryOffset number of first byte to be written by copy operation
   * @param byteArray byte array to copy from
   * @param byteArrayOffset number of first byte in byteArray to copy
   * @param count number of bytes to copy
   * @return number of bytes written
   */
  fun write(memoryOffset: Int, byteArray: ByteArray, byteArrayOffset: Int, count: Int): Int

  /**
   * Copy bytes from memory to byte array.
   *
   * @param memoryOffset number of first byte to copy
   * @param byteArray byte array to copy to
   * @param byteArrayOffset number of first byte in byte array to be written
   * @param count number of bytes to copy
   * @return number of bytes read
   */
  fun read(memoryOffset: Int, byteArray: ByteArray, byteArrayOffset: Int, count: Int): Int

  /**
   * Read byte at given offset.
   *
   * @param offset The offset from which the byte will be read
   * @return byte at given offset
   */
  fun read(offset: Int): Byte

  /**
   * Copy bytes from buffer memory wrapped by this MemoryChunk instance to buffer memory wrapped by
   * another MemoryChunk. The two MemoryChunks should have the same type.
   *
   * @param offset number of first byte to copy
   * @param other other MemoryChunk to copy to
   * @param otherOffset number of first byte to write to
   * @param count number of bytes to copy
   * @throws IllegalArgumentException if the memory chunks don't have the same type
   */
  fun copy(offset: Int, other: MemoryChunk, otherOffset: Int, count: Int)

  /**
   * Gets the pointer the native memory.
   *
   * @throws UnsupportedOperationException if the memory chunk is not in native memory
   */
  @get:Throws(UnsupportedOperationException::class) val nativePtr: Long

  /** Gets the ByteBuffer associated with the memory chunk if available, else null. */
  val byteBuffer: ByteBuffer?

  /** Gets the unique identifier associated with the memory chunk. */
  val uniqueId: Long
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import com.facebook.common.internal.Preconditions

object MemoryChunkUtil {

  /**
   * Computes number of bytes that can be safely read/written starting at given offset, but no more
   * than count.
   */
  @JvmStatic
  fun adjustByteCount(offset: Int, count: Int, memorySize: Int): Int {
    val available = Math.max(0, memorySize - offset)
    return Math.min(available, count)
  }

  /** Check that copy/read/write operation won't access memory it should not */
  @JvmStatic
  fun checkBounds(offset: Int, otherLength: Int, otherOffset: Int, count: Int, memorySize: Int) {
    Preconditions.checkArgument(count >= 0, "count (%d) ! >= 0", count)
    Preconditions.checkArgument(offset >= 0, "offset (%d) ! >= 0", offset)
    Preconditions.checkArgument(otherOffset >= 0, "otherOffset (%d) ! >= 0", otherOffset)
    Preconditions.checkArgument(
        offset + count <= memorySize, "offset (%d) + count (%d) ! <= %d", offset, count, memorySize)
    Preconditions.checkArgument(
        otherOffset + count <= otherLength,
        "otherOffset (%d) + count (%d) ! <= %d",
        otherOffset,
        count,
        otherLength)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import androidx.annotation.VisibleForTesting
import com.facebook.common.internal.Throwables
import com.facebook.common.memory.PooledByteBufferFactory
import com.facebook.common.memory.PooledByteStreams
import com.facebook.common.references.CloseableReference
import java.io.IOException
import java.io.InputStream
import javax.annotation.concurrent.ThreadSafe

/**
 * A factory to provide instances of [MemoryPooledByteBuffer] and
 * [MemoryPooledByteBufferOutputStream]
 */
@ThreadSafe
class MemoryPooledByteBufferFactory( // memory pool
private val pool: MemoryChunkPool, private val pooledByteStreams: PooledByteStreams) :
    PooledByteBufferFactory {

  override fun newByteBuffer(size: Int): MemoryPooledByteBuffer {
    check(size > 0)
    val chunkRef = CloseableReference.of(pool[size], pool)
    return try {
      MemoryPooledByteBuffer(chunkRef, size)
    } finally {
      chunkRef.close()
    }
  }

  @Throws(IOException::class)
  override fun newByteBuffer(inputStream: InputStream): MemoryPooledByteBuffer {
    val outputStream = MemoryPooledByteBufferOutputStream(pool)
    return try {
      newByteBuf(inputStream, outputStream)
    } finally {
      outputStream.close()
    }
  }

  override fun newByteBuffer(bytes: ByteArray): MemoryPooledByteBuffer {
    val outputStream = MemoryPooledByteBufferOutputStream(pool, bytes.size)
    return try {
      outputStream.write(bytes, 0, bytes.size)
      outputStream.toByteBuffer()
    } catch (ioe: IOException) {
      throw Throwables.propagate(ioe)
    } finally {
      outputStream.close()
    }
  }

  @Throws(IOException::class)
  override fun newByteBuffer(
      inputStream: InputStream,
      initialCapacity: Int
  ): MemoryPooledByteBuffer {
    val outputStream = MemoryPooledByteBufferOutputStream(pool, initialCapacity)
    return try {
      newByteBuf(inputStream, outputStream)
    } finally {
      outputStream.close()
    }
  }

  /**
   * Reads all bytes from inputStream and writes them to outputStream. When all bytes are read
   * outputStream.toByteBuffer is called and obtained MemoryPooledByteBuffer is returned
   *
   * @param inputStream the input stream to read from
   * @param outputStream output stream used to transform content of input stream to
   *   MemoryPooledByteBuffer
   * @return an instance of MemoryPooledByteBuffer
   * @throws IOException
   */
  @VisibleForTesting
  @Throws(IOException::class)
  fun newByteBuf(
      inputStream: InputStream,
      outputStream: MemoryPooledByteBufferOutputStream
  ): MemoryPooledByteBuffer {
    pooledByteStreams.copy(inputStream, outputStream)
    return outputStream.toByteBuffer()
  }

  override fun newOutputStream(): MemoryPooledByteBufferOutputStream =
      MemoryPooledByteBufferOutputStream(pool)

  override fun newOutputStream(initialCapacity: Int): MemoryPooledByteBufferOutputStream =
      MemoryPooledByteBufferOutputStream(pool, initialCapacity)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

import androidx.annotation.VisibleForTesting
import com.facebook.common.memory.PooledByteBufferOutputStream
import com.facebook.common.references.CloseableReference
import java.io.IOException
import javax.annotation.concurrent.NotThreadSafe

/** An implementation of [PooledByteBufferOutputStream] that produces a [MemoryPooledByteBuffer] */
@NotThreadSafe
class MemoryPooledByteBufferOutputStream
@JvmOverloads
constructor(pool: MemoryChunkPool, initialCapacity: Int = pool.minBufferSize) :
    PooledByteBufferOutputStream() {

  private val pool // the pool to allocate memory chunks from
  : MemoryChunkPool
  private var bufRef // the current chunk that we're writing to
  : CloseableReference<MemoryChunk>?
  private var count // number of bytes 'used' in the current chunk
  : Int

  /**
   * Construct a new instance of this OutputStream
   *
   * @param pool the pool to use
   */
  init {
    check(initialCapacity > 0)
    this.pool = checkNotNull(pool)
    count = 0
    bufRef = CloseableReference.of(this.pool[initialCapacity], this.pool)
  }

  /**
   * Gets a PooledByteBuffer from the current contents. If the stream has already been closed, then
   * an InvalidStreamException is thrown.
   *
   * @return a PooledByteBuffer instance for the contents of the stream
   * @throws InvalidStreamException if the stream is invalid
   */
  override fun toByteBuffer(): MemoryPooledByteBuffer {
    ensureValid()
    return MemoryPooledByteBuffer(checkNotNull(bufRef), count)
  }

  /**
   * Returns the total number of bytes written to this stream so far.
   *
   * @return the number of bytes written to this stream.
   */
  override fun size(): Int = count

  /**
   * Write one byte to the underlying stream. The underlying stream MUST be valid
   *
   * @param oneByte the one byte to write
   * @throws InvalidStreamException if the stream is invalid
   * @throws IOException in case of an I/O error during the write
   */
  @Throws(IOException::class)
  override fun write(oneByte: Int) {
    val buf = ByteArray(1)
    buf[0] = oneByte.toByte()
    this.write(buf)
  }

  /**
   * Writes `count` bytes from the byte array `buffer` starting at position `offset` to this stream.
   * The underlying stream MUST be valid
   *
   * @param buffer the source buffer to read from
   * @param offset the start position in `buffer` from where to get bytes.
   * @param count the number of bytes from `buffer` to write to this stream.
   * @throws IOException if an error occurs while writing to this stream.
   * @throws IndexOutOfBoundsException if `offset < 0` or `count < 0`, or if `offset + count` is
   *   bigger than the length of `buffer`.
   * @throws InvalidStreamException if the stream is invalid
   */
  @Throws(IOException::class)
  override fun write(buffer: ByteArray, offset: Int, count: Int) {
    if (offset < 0 || count < 0 || offset + count > buffer.size) {
      throw ArrayIndexOutOfBoundsException(
          "length=${buffer.size}; regionStart=${offset}; regionLength=${count}")
    }
    ensureValid()
    realloc(this.count + count)
    checkNotNull(bufRef).get().write(this.count, buffer, offset, count)
    this.count += count
  }

  /**
   * Closes the stream. Owned resources are released back to the pool. It is not allowed to call
   * toByteBuffer after call to this method.
   *
   * @throws IOException
   */
  override fun close() {
    CloseableReference.closeSafely(bufRef)
    bufRef = null
    count = -1
    super.close()
  }

  /**
   * Reallocate the local buffer to hold the new length specified. Also copy over existing data to
   * this new buffer
   *
   * @param newLength new length of buffer
   * @throws InvalidStreamException if the stream is invalid
   * @throws BasePool.SizeTooLargeException if the allocation from the pool fails
   */
  @VisibleForTesting
  fun realloc(newLength: Int) {
    ensureValid()
    checkNotNull(bufRef)
    /* Can the buffer handle @i more bytes, if not expand it */ if (newLength <=
        bufRef!!.get().size) {
      return
    }
    val newbuf = this.pool[newLength]
    checkNotNull(bufRef)
    bufRef!!.get().copy(0, newbuf, 0, count)
    bufRef!!.close()
    bufRef = CloseableReference.of(newbuf, this.pool)
  }

  /**
   * Ensure that the current stream is valid, that is underlying closeable reference is not null and
   * is valid
   *
   * @throws InvalidStreamException if the stream is invalid
   */
  private fun ensureValid() {
    if (!CloseableReference.isValid(bufRef)) {
      throw InvalidStreamException()
    }
  }

  /** An exception indicating that this stream is no longer valid */
  class InvalidStreamException : RuntimeException("OutputStream no longer valid")
  /**
   * Construct a new instance of this output stream with this initial capacity It is not an error to
   * have this initial capacity be inaccurate. If the actual contents end up being larger than the
   * initialCapacity, then we will reallocate memory if needed. If the actual contents are smaller,
   * then we'll end up wasting some memory
   *
   * @param pool the pool to use
   * @param initialCapacity initial capacity to allocate for this stream
   */
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/**
 * Pool implementations for the image pipeline. Here we have the implementations which use the
 * native memory code about MemChunk.
 */
package com.facebook.imagepipeline.memory


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

/**
 * Manages pooled objects
 *
 * @param <T> type of pooled objects </T>
 */
internal interface PoolBackend<T> {

  /** @return available object from the pool or null */
  operator fun get(size: Int): T?

  fun put(item: T)

  /** @return size for item which will be used in [get(int)] */
  fun getSize(item: T): Int

  /**
   * Removed a single object (if any) from the pool
   *
   * @return the removed object or null
   */
  fun pop(): T?
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory

/** Listener that logs pool statistics. */
interface PoolStatsTracker {

  fun setBasePool(basePool: BasePool<*>)

  fun onValueReuse(bucketedSize: Int)

  fun onSoftCapReached()

  fun onHardCapReached()

  fun onAlloc(size: Int)

  fun onFree(sizeInBytes: Int)

  fun onValueRelease(sizeInBytes: Int)

  companion object {
    const val BUCKETS_USED_PREFIX = "buckets_used_"
    const val USED_COUNT = "used_count"
    const val USED_BYTES = "used_bytes"
    const val FREE_COUNT = "free_count"
    const val FREE_BYTES = "free_bytes"
    const val SOFT_CAP = "soft_cap"
    const val HARD_CAP = "hard_cap"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.platform

import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.graphics.BitmapFactory.Options
import android.os.Build
import androidx.core.util.Pools
import com.facebook.imagepipeline.memory.BitmapPool
import com.facebook.imageutils.BitmapUtil
import java.nio.ByteBuffer
import javax.annotation.concurrent.ThreadSafe

/** Bitmap decoder for ART VM (Lollipop and up). */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ThreadSafe
class ArtDecoder(bitmapPool: BitmapPool, decodeBuffers: Pools.Pool<ByteBuffer>) :
    DefaultDecoder(bitmapPool, decodeBuffers) {
  override fun getBitmapSize(width: Int, height: Int, options: Options): Int {
    @SuppressLint("RestrictedApi") val c = checkNotNull(options.inPreferredConfig)
    return BitmapUtil.getSizeInByteForBitmap(width, height, c)
  }
}