/*
 * 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.cache.common.CacheKey
import com.facebook.cache.common.CacheKeyUtil
import com.facebook.fresco.ui.common.ImageLoadStatus
import com.facebook.fresco.ui.common.ImagePerfData
import com.facebook.fresco.ui.common.ImagePerfDataListener
import com.facebook.fresco.ui.common.VisibilityState
import com.facebook.imagepipeline.request.ImageRequest

private const val MAX_IMAGES_TO_TRACK = 1_000

/** Fresco image tracker for Sonar */
class FlipperImageTracker : DebugImageTracker, ImagePerfDataListener {

  private val imageRequestDebugDataMap: MutableMap<ImageRequest?, ImageDebugData> =
      LruMap(MAX_IMAGES_TO_TRACK)
  private val imageDebugDataMap: MutableMap<CacheKey, ImageDebugData> = LruMap(MAX_IMAGES_TO_TRACK)

  @Synchronized
  override fun trackImage(imageRequest: ImageRequest?, cacheKey: CacheKey) {
    var imageDebugData = imageRequestDebugDataMap[imageRequest]
    if (imageDebugData == null) {
      imageDebugData = ImageDebugData(imageRequest)
      imageDebugDataMap[cacheKey] = imageDebugData
      imageRequestDebugDataMap[imageRequest] = imageDebugData
    }
    imageDebugData.addCacheKey(cacheKey)
    imageDebugData.addResourceId(CacheKeyUtil.getFirstResourceId(cacheKey))
  }

  @Synchronized
  override fun trackImageRequest(imageRequest: ImageRequest?, requestId: String?) {
    var imageDebugData = imageRequestDebugDataMap[imageRequest]
    if (imageDebugData == null) {
      imageDebugData = ImageDebugData(imageRequest)
      imageRequestDebugDataMap[imageRequest] = imageDebugData
    }
    imageDebugData.addRequestId(requestId)
  }

  @Synchronized
  fun trackImage(localPath: String?, key: CacheKey): ImageDebugData {
    val data = ImageDebugData(localPath)
    imageDebugDataMap[key] = data
    return data
  }

  @Synchronized
  fun trackImage(key: CacheKey): ImageDebugData {
    val data = ImageDebugData()
    imageDebugDataMap[key] = data
    return data
  }

  @Synchronized
  fun getUriString(key: CacheKey): String {
    val imageDebugData = getImageDebugData(key)
    if (imageDebugData != null) {
      val imageRequest = imageDebugData.imageRequest
      if (imageRequest != null) {
        return imageRequest.sourceUri.toString()
      }
    }
    return key.uriString
  }

  @Synchronized
  fun getLocalPath(key: CacheKey): String? {
    val imageDebugData = getImageDebugData(key)
    return imageDebugData?.localPath
  }

  @Synchronized fun getImageDebugData(key: CacheKey): ImageDebugData? = imageDebugDataMap[key]

  @Synchronized
  fun getDebugDataForRequestId(requestId: String?): ImageDebugData? {
    for (debugData in imageRequestDebugDataMap.values) {
      val requestIds = debugData.requestIds
      if (requestIds != null && requestIds.contains(requestId)) {
        return debugData
      }
    }
    return null
  }

  @Synchronized
  fun getDebugDataForResourceId(resourceId: String): ImageDebugData? {
    for (debugData in imageRequestDebugDataMap.values) {
      val ids = debugData.resourceIds
      if (ids != null && ids.contains(resourceId)) {
        return debugData
      }
    }
    return null
  }

  @Synchronized
  fun getCacheKey(imageId: String): CacheKey? {
    for ((key, value) in imageDebugDataMap) {
      if (value.uniqueId == imageId) {
        return key
      }
    }
    return null
  }

  @Synchronized
  override fun onImageLoadStatusUpdated(
      imagePerfData: ImagePerfData,
      imageLoadStatus: ImageLoadStatus
  ) {
    if (imagePerfData?.imageRequest == null) {
      return
    }
    val debugData = imageRequestDebugDataMap[imagePerfData.imageRequest]
    if (debugData != null) {
      debugData.imagePerfData = imagePerfData
    } else {
      val imageRequest = imagePerfData?.imageRequest as? ImageRequest
      val imageDebugData = ImageDebugData(imageRequest)
      imageDebugData.imagePerfData = imagePerfData
      imageRequestDebugDataMap[imageRequest] = imageDebugData
    }
  }

  @Synchronized
  override fun onImageVisibilityUpdated(
      imagePerfData: ImagePerfData,
      visibilityState: VisibilityState
  ) {
    // ignore
  }

  class ImageDebugData
  @JvmOverloads
  constructor(val imageRequest: ImageRequest? = null, val localPath: String? = null) {
    var imagePerfData: ImagePerfData? = null
    private var _cacheKeys: MutableSet<CacheKey>? = null
    private var _requestIds: MutableSet<String?>? = null
    private var _resourceIds: MutableSet<String>? = null

    constructor(localPath: String?) : this(null, localPath) {}

    val cacheKeys: Set<CacheKey>?
      get() = _cacheKeys

    fun addCacheKey(cacheKey: CacheKey) {
      if (_cacheKeys == null) {
        _cacheKeys = HashSet()
      }
      _cacheKeys?.add(cacheKey)
    }

    val requestIds: Set<String?>?
      get() = _requestIds

    val uniqueId: String
      get() = hashCode().toString()

    fun addRequestId(requestId: String?) {
      if (_requestIds == null) {
        _requestIds = HashSet()
      }
      _requestIds?.add(requestId)
    }

    fun addResourceId(resourceId: String?) {
      if (resourceId == null) {
        return
      }
      if (_resourceIds == null) {
        _resourceIds = HashSet()
      }
      _resourceIds?.add(resourceId)
    }

    val resourceIds: Set<String>?
      get() = _resourceIds
  }
}


/*
 * 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 java.util.LinkedHashMap

/**
 * A map that provides a constant-size LRU map by ordering elements by accessing order (and not in
 * insertion order) Most cases would be served better by using Android's LruCache class.
 */
class LruMap<A, B>(private val maxEntries: Int) : LinkedHashMap<A, B>(maxEntries + 1, 1.0f, true) {
  override fun removeEldestEntry(eldest: Map.Entry<A, B>): Boolean = size > maxEntries
}


/*
 * 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.cache.common.CacheKey
import com.facebook.imagepipeline.request.ImageRequest

/** No-op debug image tracker. */
class NoOpDebugImageTracker : DebugImageTracker {

  override fun trackImage(imageRequest: ImageRequest?, cacheKey: CacheKey) {
    // no-op
  }

  override fun trackImageRequest(imageRequest: ImageRequest?, requestId: String?) {
    // no-op
  }
}


/*
 * 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.stetho

import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.os.Environment
import android.util.SparseIntArray
import com.facebook.cache.common.CacheKey
import com.facebook.cache.disk.DiskStorage.DiskDumpInfo
import com.facebook.cache.disk.DiskStorage.DiskDumpInfoEntry
import com.facebook.cache.disk.FileCache
import com.facebook.common.time.RealtimeSinceBootClock
import com.facebook.imagepipeline.cache.BitmapMemoryCacheKey
import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector
import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector.DumpInfo
import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector.DumpInfoEntry
import com.facebook.imagepipeline.core.ImagePipeline
import com.facebook.imagepipeline.core.ImagePipelineFactory
import com.facebook.imagepipeline.image.CloseableBitmap
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.stetho.dumpapp.DumpException
import com.facebook.stetho.dumpapp.DumpUsageException
import com.facebook.stetho.dumpapp.DumperContext
import com.facebook.stetho.dumpapp.DumperPlugin
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintStream
import java.util.ArrayList

/**
 * Base class for the Fresco Stetho plugin.
 *
 * Applications should instantiate
 */
abstract class BaseFrescoStethoPlugin() : DumperPlugin {

  protected var initialized = false
  private var bitmapMemoryCacheInspector: CountingMemoryCacheInspector<CacheKey, CloseableImage>? =
      null
  private var mainFileCache: FileCache? = null
  private var smallFileCache: FileCache? = null
  private var imagePipeline: ImagePipeline? = null

  protected constructor(factory: ImagePipelineFactory) : this() {
    initialize(factory)
  }

  @Throws(DumpException::class) protected abstract fun ensureInitialized()

  protected fun initialize(factory: ImagePipelineFactory) {
    bitmapMemoryCacheInspector = CountingMemoryCacheInspector(factory.bitmapCountingMemoryCache)
    mainFileCache = factory.mainFileCache
    smallFileCache = factory.smallImageFileCache
    imagePipeline = factory.imagePipeline
    initialized = true
  }

  override fun getName(): String = NAME

  /**
   * Entry point for the Stetho dumpapp script.
   *
   * [initialize] must have been called in the app before running dumpapp.
   */
  @Throws(DumpException::class)
  override fun dump(dumpContext: DumperContext) {
    ensureInitialized()
    val args = dumpContext.argsAsList
    val writer = dumpContext.stdout
    val cmd = if (args.isEmpty()) null else args[0]
    val rest: List<String> = if (args.isEmpty()) ArrayList() else args.subList(1, args.size)
    when {
      cmd != null && cmd == "memcache" -> memcache(writer, rest)
      cmd != null && cmd == "diskcache" -> {
        val mainFileCache = checkNotNull(this.mainFileCache)
        val smallFileCache = checkNotNull(this.smallFileCache)

        diskcache(mainFileCache, "Main", writer, rest)
        diskcache(smallFileCache, "Small", writer, rest)
      }
      cmd != null && cmd == "clear" -> {
        val imagePipeline = checkNotNull(this.imagePipeline)
        imagePipeline.clearCaches()
      }
      else -> {
        usage(writer)
        if (cmd.isNullOrEmpty()) {
          throw DumpUsageException("Missing command")
        } else {
          throw DumpUsageException("Unknown command: $cmd")
        }
      }
    }
  }

  @Throws(DumpException::class)
  private fun diskcache(cache: FileCache, title: String, writer: PrintStream, args: List<String>) {
    val intDiskDumpInfo =
        try {
          cache.dumpInfo
        } catch (e: IOException) {
          throw DumpException(e.message)
        }
    if (args.isNotEmpty() && args[0] == "-s") {
      writeDiskDumpInfoScriptReadable(writer, intDiskDumpInfo)
    } else {
      writer.println()
      writer.println("$title disk cache contents:")
      writeDiskDumpInfo(writer, intDiskDumpInfo)
    }
  }

  private fun writeDiskDumpInfo(writer: PrintStream, dumpInfo: DiskDumpInfo) {
    if (dumpInfo.entries.isEmpty()) {
      writer.println("Empty")
      return
    }
    val histogram = emptyHistogram()
    var total = 0f
    for (entry in dumpInfo.entries) {
      writeDiskDumpEntry(writer, entry)
      addToHistogram(histogram, entry)
      total += entry.size
    }
    writer.println()
    writer.println(formatStrLocaleSafe("Total size: %.1f MB", total / 1_024 / KB))
    printFileTypes(writer, dumpInfo)
    printHistogram(writer, histogram)
  }

  private fun printFileTypes(writer: PrintStream, dumpInfo: DiskDumpInfo) {
    writer.println()
    writer.println("File Type Counts:")
    for (type in dumpInfo.typeCounts.keys) {
      val typeCounts = checkNotNull(dumpInfo.typeCounts[type])
      writer.println(formatStrLocaleSafe("%4s: %5d", type, typeCounts))
    }
  }

  private fun addToHistogram(histogram: SparseIntArray, entry: DiskDumpInfoEntry) {
    for (i in 0 until histogram.size()) {
      val key = histogram.keyAt(i)
      if (entry.size / KB < key) {
        histogram.put(key, histogram[key] + 1)
        return
      }
    }
    // big
    histogram.put((entry.size / KB).toInt(), 1)
  }

  private fun printHistogram(writer: PrintStream, histogram: SparseIntArray) {
    writer.println()
    writer.println("File Size Counts:")
    for (i in 1 until histogram.size()) {
      val lb = histogram.keyAt(i - 1)
      val ub = histogram.keyAt(i)
      writer.println(formatStrLocaleSafe("%4d-%4dK: %3d", lb, ub, histogram[ub]))
    }
  }

  private fun writeDiskDumpEntry(writer: PrintStream, entry: DiskDumpInfoEntry) {
    if (entry.firstBits != null && entry.firstBits.isNotEmpty()) {
      writer.println("Undefined: ${entry.firstBits}")
    }
    writer.println(
        formatStrLocaleSafe(
            "type: %5s size: %7.2fkB path: %9s", entry.type, entry.size / KB, entry.path))
  }

  private fun writeDiskDumpInfoScriptReadable(writer: PrintStream, dumpInfo: DiskDumpInfo) {
    for (entry in dumpInfo.entries) {
      writeDiskDumpEntryScriptReadable(writer, entry)
    }
  }

  private fun writeDiskDumpEntryScriptReadable(writer: PrintStream, entry: DiskDumpInfoEntry) {
    writer.println(formatStrLocaleSafe("%s\t%s", entry.type, entry.path))
  }

  private fun writeCacheEntry(writer: PrintStream, entry: DumpInfoEntry<CacheKey, CloseableImage>) {
    if (entry.key !is BitmapMemoryCacheKey) {
      writer.println("Undefined: ${entry.key.javaClass}")
    }
    val cacheKey = entry.key as BitmapMemoryCacheKey
    val entryValue = checkNotNull(entry.value)
    writer.println(
        formatStrLocaleSafe(
            "size: %7.2fkB (%4d x %4d) key: %s, %s, duration: %dms",
            entryValue.get().sizeInBytes / KB,
            entryValue.get().width,
            entryValue.get().height,
            entry.key,
            checkNotNull(cacheKey.callerContext),
            RealtimeSinceBootClock.get().now() - cacheKey.inBitmapCacheSince))
  }

  @Throws(DumpException::class)
  private fun memcache(writer: PrintStream, args: List<String>) {
    val bitmapMemoryCacheInspector = checkNotNull(this.bitmapMemoryCacheInspector)
    val dumpInfo: DumpInfo<CacheKey, CloseableImage> = bitmapMemoryCacheInspector.dumpCacheContent()
    try {
      writer.println(bitmapMemoryCacheInspector.javaClass.simpleName)
      writer.println()
      writer.println("Params:")
      writer.println(
          formatStrLocaleSafe("Max size:          %7.2fMB", dumpInfo.maxSize / (1_024.0 * KB)))
      writer.println(formatStrLocaleSafe("Max entries count: %9d", dumpInfo.maxEntriesCount))
      writer.println(
          formatStrLocaleSafe("Max entry size:    %7.2fMB", dumpInfo.maxEntrySize / (1_024.0 * KB)))
      writer.println()
      writer.println("Summary of current content:")
      writer.println(
          formatStrLocaleSafe(
              "Total size:        %7.2fMB (includes in-use content)",
              dumpInfo.size / (1_024.0 * KB)))
      writer.println(
          formatStrLocaleSafe(
              "Entries count:     %9d", dumpInfo.lruEntries.size + dumpInfo.sharedEntries.size))
      writer.println(
          formatStrLocaleSafe("LRU size:          %7.2fMB", dumpInfo.lruSize / (1_024.0 * KB)))
      writer.println(formatStrLocaleSafe("LRU count:         %9d", dumpInfo.lruEntries.size))
      writer.println(
          formatStrLocaleSafe(
              "Shared size:       %7.2fMB", (dumpInfo.size - dumpInfo.lruSize) / (1_024.0 * KB)))
      writer.println(formatStrLocaleSafe("Shared count:      %9d", dumpInfo.sharedEntries.size))
      writer.println()
      writer.println("The cache consists of two parts: Things currently being used and things not.")
      writer.println("Those things that are *not* currently being used are in the LRU.")
      writer.println("Things currently being used are considered to be shared. They will be added")
      writer.println("to the LRU if/when they stop being used.")
      writer.println()
      writer.println("LRU contents: (things near the top will be evicted first)")
      for (entry in dumpInfo.lruEntries) {
        writeCacheEntry(writer, entry)
      }
      writer.println()
      writer.println("Shared contents:")
      for (entry in dumpInfo.sharedEntries) {
        writeCacheEntry(writer, entry)
      }
      if (args.isNotEmpty() && "-g" == args[0]) {
        getFiles(writer, dumpInfo)
      }
    } catch (e: IOException) {
      throw DumpException(e.message)
    } finally {
      dumpInfo.release()
    }
  }

  @SuppressWarnings("ExternalStorageUse")
  @Throws(DumpException::class, IOException::class)
  private fun getFiles(writer: PrintStream, dumpInfo: DumpInfo<CacheKey, CloseableImage>) {
    writer.println("\nStoring all images in the memory cache into /sdcard/imagedumperfiles/ ...")
    val dir = File("${Environment.getExternalStorageDirectory().path}/imagedumperfiles/")
    if (dir.exists() && dir.isDirectory) {
      val files = dir.listFiles()
      if (files != null) {
        for (file in files) {
          file.delete()
        }
      }
      if (!dir.delete()) {
        throw DumpException("Failed to clear existing /sdcard/imagedumperfiles directory")
      }
    }
    if (!dir.mkdirs()) {
      throw DumpException("Failed to create /sdcard/imagedumperfiles directory")
    }
    if (dumpInfo.lruEntries.isNotEmpty()) {
      writer.println("LRU Entries:")
      storeEntries(dumpInfo.lruEntries, 1, writer, dir)
    }
    if (dumpInfo.sharedEntries.isNotEmpty()) {
      writer.println("Shared Entries:")
      storeEntries(dumpInfo.sharedEntries, dumpInfo.lruEntries.size + 1, writer, dir)
    }
    writer.println("Done!")
  }

  @Throws(IOException::class)
  private fun storeEntries(
      entries: List<DumpInfoEntry<CacheKey, CloseableImage>>,
      i: Int,
      writer: PrintStream,
      directory: File
  ) {
    var i = i
    var filename: String
    for (entry in entries) {
      val entryValue = checkNotNull(entry.value)
      val closeableImage = entryValue.get()
      if (closeableImage is CloseableBitmap) {
        filename = "tmp$i.png"
        writer.println(
            formatStrLocaleSafe("Storing image %d as %s. Key: %s", i, filename, entry.key))
        storeImage(
            closeableImage.underlyingBitmap, File(directory, filename), CompressFormat.PNG, 100)
      } else {
        writer.println(
            formatStrLocaleSafe(
                "Image %d has unrecognized type %s. Key: %s", i, closeableImage, entry.key))
      }
      i++
    }
  }

  @Throws(IOException::class)
  private fun storeImage(
      image: Bitmap,
      pictureFile: File,
      compressionFormat: CompressFormat,
      quality: Int
  ) {
    var fos: FileOutputStream? = null
    try {
      fos = FileOutputStream(pictureFile)
      image.compress(compressionFormat, quality, fos)
    } catch (e: FileNotFoundException) {
      throw IOException(e.message)
    } finally {
      fos?.close()
    }
  }

  companion object {
    private const val NAME = "image"
    private const val KB = 1_024f

    private fun emptyHistogram(): SparseIntArray {
      val histogram = SparseIntArray()
      histogram.put(0, 0)
      histogram.put(5, 0)
      histogram.put(10, 0)
      histogram.put(20, 0)
      histogram.put(50, 0)
      histogram.put(100, 0)
      histogram.put(200, 0)
      histogram.put(512, 0)
      histogram.put(1_024, 0)
      return histogram
    }

    private fun usage(writer: PrintStream) {
      val cmdName = "dumpapp ${NAME}"
      val usagePrefix = "Usage: $cmdName "
      writer.println("$usagePrefix<command> [command-options]")
      writer.println("${usagePrefix}memcache|diskcache")
      writer.println()
      writer.println("$cmdName memcache: Show contents of bitmap memory cache.")
      writer.println(
          "$cmdName memcache -g: Get contents of bitmap memory cache and store themon the sdcard.")
      writer.println("$cmdName diskcache: Show contents of disk storage cache.")
      writer.println(
          "$cmdName diskcache -s: Show contents of disk storage cache formatted for script consumption.")
      writer.println("$cmdName clear: Clear all caches.")
      writer.println()
    }

    private fun formatStrLocaleSafe(format: String, vararg args: Any): String =
        String.format(/* locale */ null, "  $format", *args)
  }
}


/*
 * 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.stetho

import com.facebook.drawee.backends.pipeline.Fresco

/**
 * Use this plugin to allow Stetho to examine the contents of Fresco's caches.
 *
 * Before running the dumpapp script from the command line, the app must already have called
 * [Stetho.initialize] and [Fresco.initialize].
 */
class FrescoStethoPlugin : BaseFrescoStethoPlugin() {
  override fun ensureInitialized() {
    if (!initialized) {
      initialize(Fresco.getImagePipelineFactory())
    }
  }
}


/*
 * 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.fresco.ui.common

data class DimensionsInfo(
    val viewportWidth: Int,
    val viewportHeight: Int,
    val encodedImageWidth: Int,
    val encodedImageHeight: Int,
    val decodedImageWidth: Int,
    val decodedImageHeight: Int,
    val scaleType: String
)


/*
 * 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.fresco.ui.common

import android.util.Log
import com.facebook.fresco.ui.common.ControllerListener2.Extras
import java.lang.Exception
import java.util.ArrayList

open class ForwardingControllerListener2<I> : BaseControllerListener2<I>() {

  private val listeners: MutableList<ControllerListener2<I>> = ArrayList(2)

  @Synchronized
  fun addListener(listener: ControllerListener2<I>) {
    listeners.add(listener)
  }

  @Synchronized
  fun removeListener(listener: ControllerListener2<I>) {
    listeners.remove(listener)
  }

  @Synchronized
  fun removeAllListeners() {
    listeners.clear()
  }

  private inline fun forEachListener(methodName: String, block: (ControllerListener2<I>) -> Unit) {
    for (i in listeners.indices) {
      val listener =
          try {
            listeners[i]
          } catch (ignore: IndexOutOfBoundsException) {
            break
          }

      try {
        block(listener)
      } catch (exception: Exception) {
        // Don't punish the other listeners if we're given a bad one.
        Log.e(TAG, "InternalListener exception in $methodName", exception)
      }
    }
  }

  override fun onSubmit(id: String, callerContext: Any?, extras: Extras?) {
    forEachListener("onSubmit") { it.onSubmit(id, callerContext, extras) }
  }

  override fun onFinalImageSet(id: String, imageInfo: I?, extraData: Extras?) {
    forEachListener("onFinalImageSet") { it.onFinalImageSet(id, imageInfo, extraData) }
  }

  override fun onFailure(id: String, throwable: Throwable?, extras: Extras?) {
    forEachListener("onFailure") { it.onFailure(id, throwable, extras) }
  }

  override fun onRelease(id: String, extras: Extras?) {
    forEachListener("onRelease") { it.onRelease(id, extras) }
  }

  override fun onIntermediateImageSet(id: String, imageInfo: I?) {
    forEachListener("onIntermediateImageSet") { it.onIntermediateImageSet(id, imageInfo) }
  }

  override fun onIntermediateImageFailed(id: String) {
    forEachListener("onIntermediateImageFailed") { it.onIntermediateImageFailed(id) }
  }

  override fun onEmptyEvent(callerContext: Any?) {
    forEachListener("onEmptyEvent") { it.onEmptyEvent(callerContext) }
  }

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


/*
 * 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.fresco.ui.common

enum class ImageLoadStatus(val value: Int) {
  UNKNOWN(-1),
  REQUESTED(0),
  INTERMEDIATE_AVAILABLE(2),
  SUCCESS(3),
  CANCELED(4),
  ERROR(5),
  DRAW(6),
  EMPTY_EVENT(7);

  /**
   * This was probably only used in open source version, so we might be able to remove this custom
   * string mapping.
   */
  override fun toString(): String {
    return when (this) {
      REQUESTED -> "requested"
      SUCCESS -> "success"
      CANCELED -> "canceled"
      INTERMEDIATE_AVAILABLE -> "intermediate_available"
      ERROR -> "error"
      else -> "unknown"
    }
  }

  companion object {
    private val VALUES = values()

    fun fromInt(value: Int) = VALUES.firstOrNull { it.value == value }
  }
}


/*
 * 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.fresco.ui.common

/**
 * Listens to ImagePerfState update notifications and turns them into ImagePerfData notifications
 */
open class ImagePerfDataNotifier(private val perfDataListener: ImagePerfDataListener) :
    ImagePerfNotifier {

  override fun notifyListenersOfVisibilityStateUpdate(
      state: ImagePerfState,
      visibilityState: VisibilityState
  ) {
    perfDataListener.onImageVisibilityUpdated(state.snapshot(), visibilityState)
  }

  override fun notifyStatusUpdated(state: ImagePerfState, imageLoadStatus: ImageLoadStatus) {
    perfDataListener.onImageLoadStatusUpdated(state.snapshot(), imageLoadStatus)
  }
}


/*
 * 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.fresco.ui.common

interface ImagePerfNotifierHolder {
  fun setImagePerfNotifier(imagePerfNotifier: ImagePerfNotifier?)
}


/*
 * 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.fresco.ui.common

interface LoggingListener {
  fun onFadeStarted(id: String)

  fun onFadeFinished(id: String)
}


/*
 * 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.fresco.ui.common

object NoOpImagePerfNotifier : ImagePerfNotifier {
  override fun notifyListenersOfVisibilityStateUpdate(
      state: ImagePerfState?,
      visibilityState: VisibilityState?
  ) = Unit

  override fun notifyStatusUpdated(state: ImagePerfState?, imageLoadStatus: ImageLoadStatus?) = Unit
}


/*
 * 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.fresco.ui.common

fun interface OnDrawControllerListener<INFO> {
  /**
   * Called when the image is drawn
   *
   * @param id controller id
   * @param imageInfo image info
   * @param dimensionsInfo viewport and encoded image dimensions
   */
  fun onImageDrawn(id: String, imageInfo: INFO, dimensionsInfo: DimensionsInfo)
}


/*
 * 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.fresco.ui.common

interface OnFadeListener {
  fun onFadeStarted()

  fun onFadeFinished()

  fun onShownImmediately()
}


/*
 * 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.fresco.ui.common

enum class VisibilityState(val value: Int) {
  UNKNOWN(-1),
  VISIBLE(1),
  INVISIBLE(2);

  companion object {
    private val VALUES = values()

    fun fromInt(value: Int) = VALUES.firstOrNull { it.value == value }
  }
}


/*
 * 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.fresco.ui.common

import java.util.concurrent.atomic.AtomicLong

object VitoUtils {

  private val idCounter = AtomicLong()

  /**
   * Create a unique Fresco Vito image ID.
   *
   * @return the new image ID to use
   */
  @JvmStatic fun generateIdentifier(): Long = idCounter.incrementAndGet()

  /** Create a string version for the given Vito ID. */
  @JvmStatic
  fun getStringId(id: Long): String =
      // Vito IDs and Drawee IDs overlap. We add a prefix to distinguish between them.
      "v$id"
}


/*
 * 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.fresco.urimod

import android.net.Uri
import com.facebook.drawee.drawable.ScalingUtils.ScaleType

data class Dimensions(val w: Int, val h: Int) {
  override fun toString() = "${w}x${h}"
}

interface UriModifierInterface {
  fun modifyUri(uri: Uri, viewport: Dimensions?, scaleType: ScaleType): Uri
}

object NopUriModifier : UriModifierInterface {
  override fun modifyUri(uri: Uri, viewport: Dimensions?, scaleType: ScaleType): Uri = uri
}

object UriModifier {
  @JvmField var INSTANCE: UriModifierInterface = NopUriModifier
}


/*
 * 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.fresco.viewport

import android.graphics.Matrix
import android.graphics.Rect

interface HasTransform {
  /**
   * Gets transformation matrix based on the scale type.
   *
   * @param outTransform out matrix to store result
   * @param parentBounds parent bounds
   * @param childWidth child width
   * @param childHeight child height
   * @param focusX focus point x coordinate, relative [0...1]
   * @param focusY focus point y coordinate, relative [0...1]
   * @return same reference to the out matrix for convenience
   */
  fun getTransform(
      outTransform: Matrix,
      parentBounds: Rect,
      childWidth: Int,
      childHeight: Int,
      focusX: Float,
      focusY: Float
  ): Matrix
}


/*
 * 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.fresco.viewport

data class ViewportData(val width: Int, val height: Int, val transform: HasTransform?)


/*
 * 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.fresco.vito.core

import com.facebook.fresco.ui.common.ControllerListener2
import com.facebook.fresco.ui.common.ImagePerfNotifier
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.imagepipeline.image.ImageInfo

interface CombinedImageListener : VitoImageRequestListener {
  fun setVitoImageRequestListener(vitoImageRequestListener: VitoImageRequestListener?)

  fun setControllerListener2(controllerListener2: ControllerListener2<ImageInfo>?)

  var imageListener: ImageListener?

  fun setImagePerfControllerListener(imagePerfControllerListener: ControllerListener2<ImageInfo>?)

  fun setLocalImagePerfStateListener(imagePerfNotifier: ImagePerfNotifier?)

  fun onReset()
}


/*
 * 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.fresco.vito.core

import com.facebook.common.internal.Supplier
import com.facebook.common.internal.Suppliers
import com.facebook.fresco.vito.core.DefaultFrescoVitoConfig.DefaultPrefetchConfig

open class DefaultFrescoVitoConfig
@JvmOverloads
constructor(override val prefetchConfig: PrefetchConfig = DefaultPrefetchConfig()) :
    FrescoVitoConfig {

  override fun submitFetchOnBgThread(): Boolean = true

  override fun useBindOnly(): Boolean = false

  override fun useNewReleaseCallback(): Boolean = false

  override fun useNativeRounding(): Supplier<Boolean>? = Suppliers.BOOLEAN_TRUE

  override fun layoutPrefetchingEnabled(callerContext: Any?): Boolean = false

  override fun useSmartPropertyDiffing(): Boolean = false

  override fun stopAnimationInOnRelease(): Boolean = false

  override fun onlyStopAnimationWhenAutoPlayEnabled(): Boolean = true

  override fun fastPathForEmptyRequests(): Boolean = false

  override fun enableWindowWideColorGamut(): Boolean = false

  open class DefaultPrefetchConfig : PrefetchConfig {
    override fun prefetchInOnPrepare(): Boolean = true

    override fun prefetchTargetOnPrepare(): PrefetchTarget = PrefetchTarget.MEMORY_DECODED

    override fun cancelOnPreparePrefetchWhenWorkingRangePrefetch(): Boolean = true

    override fun cancelPrefetchWhenFetched(): Boolean = true

    override fun prefetchWithWorkingRange(): Boolean = true

    override fun prefetchWorkingRangeSize(): Int = 3

    override fun prefetchTargetWorkingRange(): PrefetchTarget = PrefetchTarget.MEMORY_DECODED

    override fun prioritizeWithWorkingRange(): 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.fresco.vito.core

import android.graphics.Rect
import android.graphics.drawable.Drawable
import com.facebook.common.callercontext.ContextChain
import com.facebook.fresco.ui.common.ImagePerfDataListener
import com.facebook.fresco.ui.common.OnFadeListener
import com.facebook.fresco.vito.listener.ImageListener

interface FrescoController2 {

  fun <T> createDrawable(): T where T : Drawable, T : FrescoDrawableInterface

  fun fetch(
      frescoDrawable: FrescoDrawableInterface,
      imageRequest: VitoImageRequest,
      callerContext: Any?,
      contextChain: ContextChain?,
      listener: ImageListener?,
      perfDataListener: ImagePerfDataListener? = null,
      onFadeListener: OnFadeListener?,
      viewportDimensions: Rect?
  ): Boolean

  fun releaseDelayed(drawable: FrescoDrawableInterface)

  fun release(drawable: FrescoDrawableInterface)

  fun releaseImmediately(drawable: FrescoDrawableInterface)
}


/*
 * 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.fresco.vito.core

import android.graphics.drawable.Drawable
import com.facebook.drawee.drawable.VisibilityCallback
import com.facebook.fresco.vito.listener.ImageListener

interface FrescoDrawableInterface {

  val imageId: Long
  var callerContext: Any?
  val imagePerfListener: VitoImagePerfListener

  fun setMutateDrawables(mutateDrawables: Boolean)

  val actualImageDrawable: Drawable?

  fun hasImage(): Boolean

  val isFetchSubmitted: Boolean
  var imageRequest: VitoImageRequest?

  fun setVisibilityCallback(visibilityCallback: VisibilityCallback?)

  var imageListener: ImageListener?

  fun setOverlayDrawable(drawable: Drawable?): Drawable?

  var extras: Any?

  /**
   * Get a runnable that can be used to refetch the previous image, if set. This can for example be
   * used to refetch an image when a View has been re-attached without the need to manually trigger
   * a fetch call again.
   *
   * @return the refetch runnable if set
   */
  var refetchRunnable: Runnable?
}


/*
 * 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.fresco.vito.core

import com.facebook.common.internal.Supplier

interface FrescoVitoConfig {
  val prefetchConfig: PrefetchConfig

  fun submitFetchOnBgThread(): Boolean

  fun useBindOnly(): Boolean

  fun useNewReleaseCallback(): Boolean

  fun useNativeRounding(): Supplier<Boolean>?

  fun layoutPrefetchingEnabled(callerContext: Any?): Boolean

  fun useSmartPropertyDiffing(): Boolean

  fun stopAnimationInOnRelease(): Boolean

  fun onlyStopAnimationWhenAutoPlayEnabled(): Boolean

  fun fastPathForEmptyRequests(): Boolean

  fun enableWindowWideColorGamut(): 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.fresco.vito.core

import android.net.Uri
import com.facebook.datasource.DataSource
import com.facebook.fresco.vito.options.DecodedImageOptions
import com.facebook.fresco.vito.options.EncodedImageOptions
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.imagepipeline.listener.RequestListener

interface FrescoVitoPrefetcher {

  /**
   * Prefetch an image to the given [PrefetchTarget]
   *
   * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down
   * images which are immediately required on screen.
   *
   * @param prefetchTarget the target to prefetch to
   * @param uri the image URI to prefetch
   * @param imageOptions the image options used to display the image
   * @param callerContext the caller context for the given image
   * @param callsite the prefetch callsite from which this request is being made, for logging
   * @return a DataSource that can safely be ignored.
   */
  fun prefetch(
      prefetchTarget: PrefetchTarget,
      uri: Uri,
      imageOptions: ImageOptions?,
      callerContext: Any?,
      callsite: String
  ): DataSource<Void>

  /**
   * Prefetch an image to the bitmap memory cache (for decoded images). In order to cancel the
   * prefetch, close the [DataSource] returned by this method.
   *
   * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down
   * images which are immediately required on screen.
   *
   * @param uri the image URI to prefetch
   * @param imageOptions the image options used to display the image
   * @param callerContext the caller context for the given image
   * @param callsite the prefetch callsite from which this request is being made, for logging
   * @return a DataSource that can safely be ignored.
   */
  fun prefetchToBitmapCache(
      uri: Uri,
      imageOptions: DecodedImageOptions?,
      callerContext: Any?,
      callsite: String
  ): DataSource<Void>

  /**
   * Prefetch an image to the encoded memory cache. In order to cancel the prefetch, close the
   * [DataSource] returned by this method.
   *
   * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down
   * images which are immediately required on screen.
   *
   * @param uri the image URI to prefetch
   * @param imageOptions the image options used to display the image
   * @param callerContext the caller context for the given image
   * @param callsite the prefetch callsite from which this request is being made, for logging
   * @return a DataSource that can safely be ignored.
   */
  fun prefetchToEncodedCache(
      uri: Uri,
      imageOptions: EncodedImageOptions?,
      callerContext: Any?,
      callsite: String
  ): DataSource<Void>

  /**
   * Prefetch an image to the disk cache. In order to cancel the prefetch, close the [DataSource]
   * returned by this method.
   *
   * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down
   * images which are immediately required on screen.
   *
   * @param uri the image URI to prefetch
   * @param imageOptions the image options used to display the image
   * @param callerContext the caller context for the given image
   * @param callsite the prefetch callsite from which this request is being made, for logging
   * @return a DataSource that can safely be ignored.
   */
  fun prefetchToDiskCache(
      uri: Uri,
      imageOptions: ImageOptions?,
      callerContext: Any?,
      callsite: String
  ): DataSource<Void>

  /**
   * Prefetch an image to the given [PrefetchTarget] using a [VitoImageRequest]. In order to cancel
   * the prefetch, close the [DataSource] returned by this method.
   *
   * Beware that if your network fetcher doesn't support priorities prefetch requests may slow down
   * images which are immediately required on screen.
   *
   * @param prefetchTarget the target to prefetch to
   * @param callerContext the caller context for the given image
   * @param requestListener optional request listener
   * @param callsite the prefetch callsite from which this request is being made, for logging
   * @return a DataSource that can safely be ignored.
   */
  fun prefetch(
      prefetchTarget: PrefetchTarget,
      imageRequest: VitoImageRequest,
      callerContext: Any?,
      requestListener: RequestListener?,
      callsite: String
  ): DataSource<Void>

  /**
   * Sets the image's relative distance to the viewport for the purpose of prioritization.
   *
   * @param distance the image's distance to the viewport. Positive values mean below the viewport,
   *   negative values mean above the viewport and zero means visible
   * @param callerContext the caller context for the iamge
   * @param uri the image URI
   * @param callsite the callsite from which this request is being made, for logging
   */
  fun setDistanceToViewport(distance: Int, callerContext: Any?, uri: Uri?, callsite: String)
}


/*
 * 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.fresco.vito.core

import android.net.Uri
import com.facebook.fresco.vito.options.DecodedImageOptions
import com.facebook.fresco.vito.options.EncodedImageOptions
import com.facebook.imagepipeline.request.ImageRequest

interface ImagePipelineUtils {

  fun buildImageRequest(uri: Uri?, imageOptions: DecodedImageOptions): ImageRequest?

  fun wrapDecodedImageRequest(
      originalRequest: ImageRequest,
      imageOptions: DecodedImageOptions
  ): ImageRequest?

  fun buildEncodedImageRequest(uri: Uri?, imageOptions: EncodedImageOptions): ImageRequest?
}


/*
 * 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.fresco.vito.core

class ListVitoImagePerfListener(private vararg val listeners: VitoImagePerfListener) :
    VitoImagePerfListener {
  override fun onImageMount(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageMount(drawable) }

  override fun onImageUnmount(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageUnmount(drawable) }

  override fun onImageBind(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageBind(drawable) }

  override fun onImageUnbind(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageUnbind(drawable) }

  override fun onImageFetch(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageFetch(drawable) }

  override fun onImageSuccess(drawable: FrescoDrawableInterface, wasImmediate: Boolean): Unit =
      listeners.forEach { it.onImageSuccess(drawable, wasImmediate) }

  override fun onImageError(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageError(drawable) }

  override fun onImageRelease(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onImageRelease(drawable) }

  override fun onScheduleReleaseDelayed(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onScheduleReleaseDelayed(drawable) }

  override fun onScheduleReleaseNextFrame(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onScheduleReleaseNextFrame(drawable) }

  override fun onReleaseImmediately(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onReleaseImmediately(drawable) }

  override fun onDrawableReconfigured(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onDrawableReconfigured(drawable) }

  override fun onIgnoreResult(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onIgnoreResult(drawable) }

  override fun onIgnoreFailure(drawable: FrescoDrawableInterface): Unit =
      listeners.forEach { it.onIgnoreFailure(drawable) }
}


/*
 * 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.fresco.vito.core

import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable

/** A Drawable that draws nothing on Canvas */
object NopDrawable : Drawable() {

  override fun draw(canvas: Canvas) {
    // nop
  }

  override fun setAlpha(alpha: Int) {
    // nop
  }

  override fun setColorFilter(colorFilter: ColorFilter?) {
    // nop
  }

  override fun getOpacity(): Int = PixelFormat.OPAQUE
}


/*
 * 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.fresco.vito.core

interface PrefetchConfig {
  fun prefetchInOnPrepare(): Boolean

  fun prefetchTargetOnPrepare(): PrefetchTarget

  fun cancelOnPreparePrefetchWhenWorkingRangePrefetch(): Boolean

  fun cancelPrefetchWhenFetched(): Boolean

  fun prefetchWithWorkingRange(): Boolean

  fun prefetchWorkingRangeSize(): Int

  fun prefetchTargetWorkingRange(): PrefetchTarget

  fun prioritizeWithWorkingRange(): 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.fresco.vito.core

enum class PrefetchReason {

  /** the image is needed right away to show on the viewport */
  ON_SCREEN,

  /** the image will be automatically become visible on the viewport after several seconds */
  ON_TIMEOUT,

  /**
   * the image will be visible when user scroll down to reveal it, highly likely if people stay on
   * the same surface
   */
  ON_SCROLL,

  /**
   * the image will not be visible until people tap on something to show a different surface than
   * the current one
   */
  ON_TAP
}


/*
 * 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.fresco.vito.core

enum class PrefetchTarget {
  MEMORY_DECODED,
  MEMORY_ENCODED,
  DISK
}


/*
 * 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.fresco.vito.core

interface VitoImagePerfListener {
  fun onImageMount(drawable: FrescoDrawableInterface)

  fun onImageUnmount(drawable: FrescoDrawableInterface)

  fun onImageBind(drawable: FrescoDrawableInterface)

  fun onImageUnbind(drawable: FrescoDrawableInterface)

  fun onImageFetch(drawable: FrescoDrawableInterface)

  fun onImageSuccess(drawable: FrescoDrawableInterface, wasImmediate: Boolean)

  fun onImageError(drawable: FrescoDrawableInterface)

  fun onImageRelease(drawable: FrescoDrawableInterface)

  fun onScheduleReleaseDelayed(drawable: FrescoDrawableInterface)

  fun onScheduleReleaseNextFrame(drawable: FrescoDrawableInterface)

  fun onReleaseImmediately(drawable: FrescoDrawableInterface)

  fun onDrawableReconfigured(drawable: FrescoDrawableInterface)

  fun onIgnoreResult(drawable: FrescoDrawableInterface)

  fun onIgnoreFailure(drawable: FrescoDrawableInterface)
}


/*
 * 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.fresco.vito.core

import android.content.res.Resources
import android.graphics.Rect
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.listener.RequestListener

interface VitoImagePipeline {

  fun createImageRequest(
      resources: Resources,
      imageSource: ImageSource,
      options: ImageOptions?,
  ): VitoImageRequest = createImageRequest(resources, imageSource, options, null)

  fun createImageRequest(
      resources: Resources,
      imageSource: ImageSource,
      options: ImageOptions?,
      viewport: Rect?
  ): VitoImageRequest

  fun getCachedImage(imageRequest: VitoImageRequest): CloseableReference<CloseableImage>?

  fun fetchDecodedImage(
      imageRequest: VitoImageRequest,
      callerContext: Any?,
      requestListener: RequestListener?,
      uiComponentId: Long
  ): DataSource<CloseableReference<CloseableImage>>
}


/*
 * 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.fresco.vito.core

import android.content.res.Resources
import com.facebook.cache.common.CacheKey
import com.facebook.common.internal.Objects
import com.facebook.fresco.middleware.Extras
import com.facebook.fresco.middleware.HasExtraData
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.imagepipeline.request.ImageRequest
import kotlin.jvm.JvmField

class VitoImageRequest(
    @JvmField val resources: Resources,
    @JvmField val imageSource: ImageSource,
    @JvmField val imageOptions: ImageOptions,
    @JvmField val finalImageRequest: ImageRequest?,
    @JvmField val finalImageCacheKey: CacheKey?,
    @JvmField val extras: MutableMap<String, Any?> = mutableMapOf(),
) : HasExtraData {
  override fun <E> putExtra(key: String, value: E?) {
    extras[key] = value
  }

  @Suppress("UNCHECKED_CAST") override fun <E> getExtra(key: String): E? = extras[key] as? E

  override fun <E> getExtra(key: String, valueIfNotFound: E?): E? = getExtra(key) ?: valueIfNotFound

  override fun getExtras(): Extras = extras

  override fun putExtras(extras: Extras) = extras.forEach { putExtra(it.key, it.value) }

  override fun equals(obj: Any?): Boolean {
    if (this === obj) {
      return true
    }
    if (obj == null || javaClass != obj.javaClass) return false
    val other = obj as VitoImageRequest
    return resources === other.resources &&
        Objects.equal(imageSource, other.imageSource) &&
        Objects.equal(imageOptions, other.imageOptions)
  }

  fun equalsIfHasImage(other: VitoImageRequest?, hasImage: Boolean): Boolean {
    if (this === other) {
      return true
    }
    if (other == null) {
      return false
    }
    return resources === other.resources &&
        Objects.equal(imageSource, other.imageSource) &&
        if (hasImage) imageOptions.equalsForActualImage(other.imageOptions)
        else Objects.equal(imageOptions, other.imageOptions)
  }

  override fun hashCode(): Int {
    var result = resources.hashCode()
    result = 31 * result + imageSource.hashCode()
    result = 31 * result + imageOptions.hashCode()
    return result
  }
}


/*
 * 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.fresco.vito.core

import android.graphics.drawable.Drawable
import com.facebook.drawee.backends.pipeline.info.ImageOrigin
import com.facebook.fresco.ui.common.ControllerListener2.Extras
import com.facebook.imagepipeline.image.ImageInfo

/** Interface for an image status listener. */
interface VitoImageRequestListener {

  /**
   * Called before the image request is submitted.
   *
   * @param id image id
   * @param callerContext caller context
   */
  fun onSubmit(id: Long, imageRequest: VitoImageRequest, callerContext: Any?, extras: Extras?)

  /**
   * Called after a placeholder image has been set
   *
   * @param id image id
   * @param placeholder the placeholder drawable if set
   */
  fun onPlaceholderSet(id: Long, imageRequest: VitoImageRequest, placeholder: Drawable?)

  /**
   * Called after the final image has been set.
   *
   * @param id image id
   * @param imageOrigin image origin that indicates where an image is being loaded from
   * @param imageInfo image info
   * @param drawable the Drawable to be displayed
   */
  fun onFinalImageSet(
      id: Long,
      imageRequest: VitoImageRequest,
      @ImageOrigin imageOrigin: Int,
      imageInfo: ImageInfo?,
      extras: Extras?,
      drawable: Drawable?
  )

  /**
   * Called after any intermediate image has been set.
   *
   * @param id image id
   * @param imageInfo image info
   */
  fun onIntermediateImageSet(id: Long, imageRequest: VitoImageRequest, imageInfo: ImageInfo?)

  /**
   * Called after the fetch of the intermediate image failed.
   *
   * @param id image id
   * @param throwable failure cause
   */
  fun onIntermediateImageFailed(id: Long, imageRequest: VitoImageRequest, throwable: Throwable?)

  /**
   * Called after the fetch of the final image failed.
   *
   * @param id image id
   * @param error the displayed error drawable if set
   * @param throwable failure cause
   */
  fun onFailure(
      id: Long,
      imageRequest: VitoImageRequest,
      error: Drawable?,
      throwable: Throwable?,
      extras: Extras?
  )

  /**
   * Called after the controller released the fetched image.
   *
   * @param id image id
   */
  fun onRelease(id: Long, imageRequest: VitoImageRequest, extras: Extras?)

  /** Called on empty event. For instance when empty URI is requested. */
  fun onEmptyEvent(callerContext: Any?)
}


/*
 * 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.fresco.vito.drawable

import android.content.res.Resources
import android.graphics.drawable.Drawable
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.options.ImageOptionsDrawableFactory
import com.facebook.imagepipeline.image.CloseableImage

class ArrayVitoDrawableFactory(private vararg val drawableFactories: ImageOptionsDrawableFactory) :
    ImageOptionsDrawableFactory {

  override fun createDrawable(
      resources: Resources,
      closeableImage: CloseableImage,
      imageOptions: ImageOptions
  ): Drawable? =
      drawableFactories.firstNotNullOfOrNull { factory ->
        factory.createDrawable(resources, closeableImage, imageOptions)
      }
}


/*
 * 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.fresco.vito.drawable

import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.media.ExifInterface
import com.facebook.drawee.drawable.OrientedDrawable
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.options.ImageOptionsDrawableFactory
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.CloseableStaticBitmap
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.systrace.FrescoSystrace.traceSection

class BitmapDrawableFactory : ImageOptionsDrawableFactory {

  override fun createDrawable(
      resources: Resources,
      closeableImage: CloseableImage,
      imageOptions: ImageOptions
  ): Drawable? =
      traceSection("BitmapDrawableFactory#createDrawable") {
        if (closeableImage is CloseableStaticBitmap) {
          handleCloseableStaticBitmap(resources, closeableImage, imageOptions)
        } else {
          null
        }
      }

  /**
   * We handle the given bitmap and return a Drawable ready for being displayed: If rounding is set,
   * the image will be rounded, if a border if set, the border will be applied and finally, the
   * image will be rotated if required.
   *
   * Bitmap -> border -> rounded corners -> RoundedBitmapDrawable (since bitmap is square) -> fully
   * circular -> CircularBorderBitmapDrawable (bitmap is circular) -> square image ->
   * RoundedBitmapDrawable (for border support) -> no border -> rounded corners ->
   * RoundedBitmapDrawable (since bitmap is square) -> fully circular -> BitmapDrawable (since
   * bitmap is circular) -> square image -> BitmapDrawable
   *
   * If needed, the resulting drawable is rotated using OrientedDrawable.
   *
   * @param closeableStaticBitmap the image to handle
   * @param imageOptions display options for the given image
   * @return the drawable to display
   */
  protected fun handleCloseableStaticBitmap(
      resources: Resources,
      closeableStaticBitmap: CloseableStaticBitmap,
      imageOptions: ImageOptions
  ): Drawable {
    val roundingOptions = imageOptions.roundingOptions
    val borderOptions = imageOptions.borderOptions
    val isBitmapRounded = true == closeableStaticBitmap.extras["is_rounded"]
    val drawable: Drawable =
        if (isBitmapRounded && roundingOptions != null && roundingOptions.isCircular) {
          if (borderOptions != null && borderOptions.width > 0) {
            CircularBorderBitmapDrawable(
                resources, closeableStaticBitmap.underlyingBitmap, borderOptions)
          } else {
            BitmapDrawable(resources, closeableStaticBitmap.underlyingBitmap)
          }
        } else {
          RoundingUtils.roundedDrawable(
              resources, closeableStaticBitmap.underlyingBitmap, borderOptions, roundingOptions)
        }
    return rotatedDrawable(closeableStaticBitmap, drawable)
  }

  protected fun rotatedDrawable(
      closeableStaticBitmap: CloseableStaticBitmap,
      drawable: Drawable
  ): Drawable =
      if (!hasTransformableRotationAngle(closeableStaticBitmap) &&
          !hasTransformableExifOrientation(closeableStaticBitmap)) {
        // Return the bitmap drawable directly as there's nothing to transform in it
        drawable
      } else {
        OrientedDrawable(
            drawable, closeableStaticBitmap.rotationAngle, closeableStaticBitmap.exifOrientation)
      }

  /* Returns true if there is anything to rotate using the rotation angle */
  private fun hasTransformableRotationAngle(closeableStaticBitmap: CloseableStaticBitmap): Boolean =
      closeableStaticBitmap.rotationAngle != 0 &&
          closeableStaticBitmap.rotationAngle != EncodedImage.UNKNOWN_ROTATION_ANGLE

  /* Returns true if there is anything to rotate using the EXIF orientation */
  private fun hasTransformableExifOrientation(
      closeableStaticBitmap: CloseableStaticBitmap
  ): Boolean =
      closeableStaticBitmap.exifOrientation != ExifInterface.ORIENTATION_NORMAL &&
          closeableStaticBitmap.exifOrientation != ExifInterface.ORIENTATION_UNDEFINED
}


/*
 * 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.fresco.vito.drawable

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import com.facebook.drawee.drawable.DrawableUtils
import com.facebook.fresco.vito.options.BorderOptions
import kotlin.math.min

class CircularBorderBitmapDrawable(
    res: Resources?,
    bitmap: Bitmap?,
    private var borderOptions: BorderOptions? = null
) : BitmapDrawable(res, bitmap) {

  private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)

  private var radius = 0
  private var _alpha = 255

  override fun draw(canvas: Canvas) {
    if (radius == 0) return
    val border = borderOptions
    if (border == null || border.padding < 0.0f || border.width < 0.0f) {
      super.draw(canvas)
      return
    }
    val widthReduction =
        if (border.scaleDownInsideBorders) border.width + border.padding else border.padding
    if (widthReduction > radius) return
    val centerX = bounds.exactCenterX()
    val centerY = bounds.exactCenterY()
    if (widthReduction > 0.0f) {
      val scale = (radius - widthReduction) / radius
      canvas.save()
      canvas.scale(scale, scale, centerX, centerY)
      super.draw(canvas)
      canvas.restore()
    } else {
      super.draw(canvas)
    }
    if (border.width > 0.0f) {
      canvas.drawCircle(centerX, centerY, radius - border.width / 2, borderPaint)
    }
  }

  override fun onBoundsChange(bounds: Rect) {
    super.onBoundsChange(bounds)
    radius = min(bounds.width(), bounds.height()) / 2
  }

  var border: BorderOptions?
    get() = borderOptions
    set(borderOptions) {
      if (this.borderOptions == null || this.borderOptions != borderOptions) {
        this.borderOptions = borderOptions
        ensureBorderPaint()
        invalidateSelf()
      }
    }

  init {
    borderPaint.style = Paint.Style.STROKE
    ensureBorderPaint()
  }

  override fun setAlpha(alpha: Int) {
    super.setAlpha(alpha)
    _alpha = alpha
    ensureBorderPaint()
  }

  private fun ensureBorderPaint() {
    borderOptions?.let {
      borderPaint.strokeWidth = it.width
      borderPaint.color = DrawableUtils.multiplyColorAlpha(it.color, _alpha)
    }
  }
}


/*
 * 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.fresco.vito.drawable

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.NinePatchDrawable
import com.facebook.drawee.drawable.Rounded
import com.facebook.drawee.drawable.RoundedBitmapDrawable
import com.facebook.drawee.drawable.RoundedColorDrawable
import com.facebook.drawee.drawable.RoundedNinePatchDrawable
import com.facebook.fresco.vito.options.BorderOptions
import com.facebook.fresco.vito.options.RoundingOptions
import java.lang.UnsupportedOperationException

/**
 * A class that contains helper methods for rounding a bitmap or different kind of Drawables. It
 * handles the conversion to the specific types of drawables.
 *
 * Different combinations are:

 * - [Bitmap] -> already rounded -> no border -> circular -> [BitmapDrawable]

 * - [Bitmap] -> already rounded -> border -> circular -> [CircularBorderBitmapDrawable]

 * - [Bitmap] -> already rounder or not -> rounded corners -> [RoundedBitmapDrawable]

 * - [BitmapDrawable] -> [RoundedBitmapDrawable]

 * - [ColorDrawable] -> [RoundedColorDrawable]

 * - [NinePatchDrawable] -> [RoundedNinePatchDrawable]

 */
object RoundingUtils {

  /**
   * Creates a drawable with the [RoundingOptions] and [BorderOptions] applied to it.
   *
   * @param bitmap a bitmap to be wrapped in the final [BitmapDrawable]
   * @param borderOptions border options for the given image
   * @param roundingOptions rounding options for the given image
   * @return a drawable with the applied effect
   */
  fun roundedDrawable(
      resources: Resources,
      bitmap: Bitmap,
      borderOptions: BorderOptions?,
      roundingOptions: RoundingOptions?
  ): Drawable =
      if (borderOptions != null && borderOptions.width > 0) {
        roundedDrawableWithBorder(resources, bitmap, borderOptions, roundingOptions)
      } else {
        roundedDrawableWithoutBorder(resources, bitmap, roundingOptions)
      }

  /**
   * Creates a drawable with the [RoundingOptions] and [BorderOptions] applied to it.
   *
   * @param drawable the image to transform
   * @param borderOptions border options for the given image
   * @param roundingOptions rounding options for the given image
   * @return a drawable with the applied effect
   */
  fun roundedDrawable(
      resources: Resources,
      drawable: Drawable,
      borderOptions: BorderOptions?,
      roundingOptions: RoundingOptions?
  ): Drawable =
      if (borderOptions != null && borderOptions.width > 0) {
        roundedDrawableWithBorder(resources, drawable, borderOptions, roundingOptions)
      } else {
        roundedDrawableWithoutBorder(resources, drawable, roundingOptions)
      }

  private fun roundedDrawableWithoutBorder(
      resources: Resources,
      bitmap: Bitmap,
      roundingOptions: RoundingOptions?
  ): Drawable =
      if (roundingOptions == null) {
        BitmapDrawable(resources, bitmap)
      } else {
        applyRounding(getRoundedDrawable(resources, bitmap), null, roundingOptions)
      }

  private fun roundedDrawableWithBorder(
      resources: Resources,
      bitmap: Bitmap,
      borderOptions: BorderOptions,
      roundingOptions: RoundingOptions?
  ): Drawable =
      if (roundingOptions == null) {
        squareDrawableWithBorder(getRoundedDrawable(resources, bitmap), borderOptions)
      } else {
        applyRounding(getRoundedDrawable(resources, bitmap), borderOptions, roundingOptions)
      }

  private fun roundedDrawableWithoutBorder(
      resources: Resources,
      drawable: Drawable,
      roundingOptions: RoundingOptions?
  ): Drawable =
      if (roundingOptions != null) {
        applyRounding(getRoundedDrawable(resources, drawable), null, roundingOptions)
      } else {
        drawable
      }

  private fun roundedDrawableWithBorder(
      resources: Resources,
      drawable: Drawable,
      borderOptions: BorderOptions,
      roundingOptions: RoundingOptions?
  ): Drawable =
      if (roundingOptions == null) {
        squareDrawableWithBorder(getRoundedDrawable(resources, drawable), borderOptions)
      } else {
        applyRounding(getRoundedDrawable(resources, drawable), borderOptions, roundingOptions)
      }

  private fun <T> getRoundedDrawable(resources: Resources, drawable: Drawable): T where
  T : Drawable,
  T : Rounded =
      when (drawable) {
        is BitmapDrawable -> getRoundedDrawable(resources, drawable.bitmap)
        is NinePatchDrawable -> RoundedNinePatchDrawable(drawable) as T
        is ColorDrawable -> RoundedColorDrawable.fromColorDrawable(drawable) as T
        else ->
            throw UnsupportedOperationException(
                "Rounding of the drawable type not supported: $drawable")
      }

  private fun <T> applyRounding(
      drawable: T,
      borderOptions: BorderOptions?,
      roundingOptions: RoundingOptions
  ): Drawable where T : Drawable, T : Rounded =
      if (!roundingOptions.isCircular) {
        roundedCornerDrawable(drawable, borderOptions, roundingOptions)
      } else {
        circularDrawable(drawable, borderOptions)
      }

  private fun <T> squareDrawableWithBorder(
      drawable: T,
      borderOptions: BorderOptions
  ): Drawable where T : Drawable, T : Rounded =
      // We use the same rounded corner drawable to draw the border without applying rounding
      roundedCornerDrawable(drawable, borderOptions, null)

  private fun <T> getRoundedDrawable(resources: Resources, bitmap: Bitmap?): T where
  T : Drawable,
  T : Rounded = RoundedBitmapDrawable(resources, bitmap) as T

  private fun <T> circularDrawable(drawable: T, borderOptions: BorderOptions?): Drawable where
  T : Drawable,
  T : Rounded {
    drawable.isCircle = true
    if (borderOptions != null) {
      applyBorders(drawable, borderOptions)
    }
    return drawable
  }

  private fun <T> roundedCornerDrawable(
      drawable: T,
      borderOptions: BorderOptions?,
      roundingOptions: RoundingOptions?
  ): Drawable where T : Drawable, T : Rounded {
    if (borderOptions != null) {
      applyBorders(drawable, borderOptions)
    }
    if (roundingOptions != null) {
      val radii = roundingOptions.cornerRadii
      if (radii != null) {
        drawable.radii = radii
      } else {
        drawable.setRadius(roundingOptions.cornerRadius)
      }
    }
    return drawable
  }

  /**
   * Applies the border according to [BorderOptions]
   *
   * @param drawable the drawable where the borders are applied
   * @param borderOptions [BorderOptions]
   */
  private fun <T> applyBorders(drawable: T, borderOptions: BorderOptions) where
  T : Drawable,
  T : Rounded {
    drawable.setBorder(borderOptions.color, borderOptions.width)
    drawable.padding = borderOptions.padding
  }
}


/*
 * 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.fresco.vito.listener

import android.graphics.drawable.Drawable
import com.facebook.drawee.backends.pipeline.info.ImageOrigin
import com.facebook.fresco.ui.common.DimensionsInfo
import com.facebook.imagepipeline.image.ImageInfo

open class BaseImageListener : ImageListener {

  override fun onSubmit(id: Long, callerContext: Any?) = Unit

  override fun onPlaceholderSet(id: Long, placeholder: Drawable?) = Unit

  override fun onFinalImageSet(
      id: Long,
      @ImageOrigin imageOrigin: Int,
      imageInfo: ImageInfo?,
      drawable: Drawable?
  ) = Unit

  override fun onIntermediateImageSet(id: Long, imageInfo: ImageInfo?) = Unit

  override fun onIntermediateImageFailed(id: Long, throwable: Throwable?) = Unit

  override fun onFailure(id: Long, error: Drawable?, throwable: Throwable?) = Unit

  override fun onRelease(id: Long) = Unit

  override fun onImageDrawn(id: String, imageInfo: ImageInfo, dimensionsInfo: DimensionsInfo) = Unit
}


/*
 * 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.fresco.vito.listener

import android.graphics.drawable.Drawable
import com.facebook.drawee.backends.pipeline.info.ImageOrigin
import com.facebook.fresco.ui.common.DimensionsInfo
import com.facebook.imagepipeline.image.ImageInfo

class ForwardingImageListener(private vararg val listeners: ImageListener?) : ImageListener {

  override fun onSubmit(id: Long, callerContext: Any?) {
    listeners.forEach { it?.onSubmit(id, callerContext) }
  }

  override fun onPlaceholderSet(id: Long, placeholder: Drawable?) {
    listeners.forEach { it?.onPlaceholderSet(id, placeholder) }
  }

  override fun onFinalImageSet(
      id: Long,
      @ImageOrigin imageOrigin: Int,
      imageInfo: ImageInfo?,
      drawable: Drawable?
  ) {
    listeners.forEach { it?.onFinalImageSet(id, imageOrigin, imageInfo, drawable) }
  }

  override fun onIntermediateImageSet(id: Long, imageInfo: ImageInfo?) {
    listeners.forEach { it?.onIntermediateImageSet(id, imageInfo) }
  }

  override fun onIntermediateImageFailed(id: Long, throwable: Throwable?) {
    listeners.forEach { it?.onIntermediateImageFailed(id, throwable) }
  }

  override fun onFailure(id: Long, error: Drawable?, throwable: Throwable?) {
    listeners.forEach { it?.onFailure(id, error, throwable) }
  }

  override fun onRelease(id: Long) {
    listeners.forEach { it?.onRelease(id) }
  }

  override fun onImageDrawn(id: String, imageInfo: ImageInfo, dimensionsInfo: DimensionsInfo) {
    listeners.forEach { it?.onImageDrawn(id, imageInfo, dimensionsInfo) }
  }

  override fun equals(o: Any?): Boolean {
    // If the object is compared with itself then return true
    if (o === this) {
      return true
    }

    // Check if o is an instance of ForwardingImageListener or not
    if (o !is ForwardingImageListener) {
      return false
    }
    val listeners = o.listeners
    if (this.listeners.size != listeners.size) {
      return false
    }
    for (i in this.listeners.indices) {
      if (this.listeners[i] != listeners[i]) {
        return false
      }
    }
    return true
  }

  override fun hashCode(): Int {
    var result = if (this.listeners[0] != null) this.listeners[0].hashCode() else 0
    for (i in 1 until this.listeners.size) {
      result = 31 * result + if (this.listeners[i] != null) this.listeners[i].hashCode() else 0
    }
    return result
  }

  companion object {
    @JvmStatic
    fun create(a: ImageListener?, b: ImageListener?): ImageListener? =
        if (a == null) {
          b
        } else {
          b?.let { ForwardingImageListener(a, it) } ?: a
        }

    @JvmStatic
    fun create(a: ImageListener?, b: ImageListener?, c: ImageListener?): ImageListener? {
      if (a == null) {
        return create(b, c)
      }
      return if (b == null) {
        create(a, c)
      } else {
        c?.let { ForwardingImageListener(a, b, it) } ?: create(a, b)
      }
    }
  }
}


/*
 * 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.fresco.vito.listener

import android.graphics.drawable.Drawable
import com.facebook.drawee.backends.pipeline.info.ImageOrigin
import com.facebook.fresco.ui.common.OnDrawControllerListener
import com.facebook.imagepipeline.image.ImageInfo

/** Interface for an image status listener. */
interface ImageListener : OnDrawControllerListener<ImageInfo> {

  /**
   * Called before the image request is submitted.
   *
   * @param id image id
   * @param callerContext caller context
   */
  fun onSubmit(id: Long, callerContext: Any?)

  /**
   * Called after a placeholder image has been set
   *
   * @param id image id
   * @param placeholder the placeholder drawable if set
   */
  fun onPlaceholderSet(id: Long, placeholder: Drawable?)

  /**
   * Called after the final image has been set.
   *
   * @param id image id
   * @param imageOrigin image origin that indicates where an image is being loaded from
   * @param imageInfo image info
   * @param drawable the Drawable to be displayed
   */
  fun onFinalImageSet(
      id: Long,
      @ImageOrigin imageOrigin: Int,
      imageInfo: ImageInfo?,
      drawable: Drawable?
  )

  /**
   * Called after any intermediate image has been set.
   *
   * @param id image id
   * @param imageInfo image info
   */
  fun onIntermediateImageSet(id: Long, imageInfo: ImageInfo?)

  /**
   * Called after the fetch of the intermediate image failed.
   *
   * @param id image id
   * @param throwable failure cause
   */
  fun onIntermediateImageFailed(id: Long, throwable: Throwable?)

  /**
   * Called after the fetch of the final image failed.
   *
   * @param id image id
   * @param error the displayed error drawable if set
   * @param throwable failure cause
   */
  fun onFailure(id: Long, error: Drawable?, throwable: Throwable?)

  /**
   * Called after the controller released the fetched image.
   *
   * @param id image id
   */
  fun onRelease(id: 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.fresco.vito.core

import android.content.res.Resources
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.source.ImageSource
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class VitoImageRequestTest {

  @Test
  fun testEquals() {
    val res = mock<Resources>()
    val imageSource = mock<ImageSource>()
    val options = mock<ImageOptions>()
    val a = VitoImageRequest(res, imageSource, options, null, null)
    val b = VitoImageRequest(res, imageSource, options, null, null)

    assertThat(a).isEqualTo(b)
  }

  @Test
  fun testHashCode() {
    val res = mock<Resources>()
    val imageSource = mock<ImageSource>()
    val options = mock<ImageOptions>()
    val a = VitoImageRequest(res, imageSource, options, null, null)
    val b = VitoImageRequest(res, imageSource, options, null, null)

    assertThat(a.hashCode()).isEqualTo(b.hashCode())
  }
}


/*
 * 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.fresco.vito.drawable

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.util.DisplayMetrics
import com.facebook.drawee.drawable.RoundedBitmapDrawable
import com.facebook.fresco.vito.options.BorderOptions
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.options.ImageOptionsDrawableFactory
import com.facebook.fresco.vito.options.RoundingOptions
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.CloseableStaticBitmap
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class BitmapDrawableFactoryTest {

  private val imageExtrasRounded: Map<String, Any> = mapOf("is_rounded" to true)
  private val imageExtrasNotRounded: Map<String, Any> = mapOf("is_rounded" to false)

  private lateinit var resources: Resources
  private lateinit var displayMetrics: DisplayMetrics
  private lateinit var drawableFactory: ImageOptionsDrawableFactory

  @Before
  fun setup() {
    resources = mock()
    displayMetrics = DisplayMetrics()
    whenever(resources.displayMetrics).thenReturn(displayMetrics)
    drawableFactory = BitmapDrawableFactory()
  }

  @Test
  fun testCreateDrawable_whenImageUnknown_thenReturnNull() {
    val closeableImage = mock<CloseableImage>()
    val options = mock<ImageOptions>()
    val drawable = drawableFactory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNull()
  }

  @Test
  fun testCreateDrawable_whenImageIsStaticBitmap_thenReturnBitmapDrawable() {
    val closeableImage = mock<CloseableStaticBitmap>()
    val bitmap = mock<Bitmap>()
    whenever(closeableImage.underlyingBitmap).thenReturn(bitmap)
    val options = mock<ImageOptions>()
    val drawable = drawableFactory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
  }

  @Test
  fun testCreateDrawable_whenRoundAsCircleAndJavaRounding_thenReturnRoundedBitmapDrawable() {
    val closeableImage = mock<CloseableStaticBitmap>()
    val bitmap = mock<Bitmap>()
    whenever(closeableImage.underlyingBitmap).thenReturn(bitmap)
    val options = mock<ImageOptions>()
    whenever(options.roundingOptions).thenReturn(RoundingOptions.asCircle())
    val factory = BitmapDrawableFactory()
    val drawable = factory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(RoundedBitmapDrawable::class.java)
  }

  @Test
  fun testCreateDrawable_whenRoundAsCircleAndNativeRounding_thenReturnBitmapDrawable() {
    val closeableImage = mock<CloseableStaticBitmap>()
    val bitmap = mock<Bitmap>()
    whenever(closeableImage.underlyingBitmap).thenReturn(bitmap)
    whenever(closeableImage.extras).thenReturn(imageExtrasRounded)
    val options = mock<ImageOptions>()
    whenever(options.roundingOptions).thenReturn(RoundingOptions.asCircle())
    val drawable = drawableFactory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
  }

  @Test
  fun testCreateDrawable_whenRoundWithCornerRadius_thenReturnBitmapDrawable() {
    val closeableImage = mock<CloseableStaticBitmap>()
    val bitmap = mock<Bitmap>()
    whenever(closeableImage.underlyingBitmap).thenReturn(bitmap)
    val options = mock<ImageOptions>()
    whenever(options.roundingOptions).thenReturn(RoundingOptions.forCornerRadiusPx(123f))
    whenever(closeableImage.extras).thenReturn(imageExtrasNotRounded)
    val drawable = drawableFactory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(RoundedBitmapDrawable::class.java)
    assertThat((drawable as RoundedBitmapDrawable?)?.radii)
        .isEqualTo(floatArrayOf(123f, 123f, 123f, 123f, 123f, 123f, 123f, 123f))
  }

  @Test
  fun testCreateDrawable_whenRoundWithCornerRadii_thenReturnBitmapDrawable() {
    val closeableImage = mock<CloseableStaticBitmap>()
    val bitmap = mock<Bitmap>()
    whenever(closeableImage.underlyingBitmap).thenReturn(bitmap)
    val options = mock<ImageOptions>()
    whenever(options.roundingOptions).thenReturn(RoundingOptions.forCornerRadii(1f, 2f, 3f, 4f))
    whenever(closeableImage.extras).thenReturn(imageExtrasNotRounded)
    val drawable = drawableFactory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(RoundedBitmapDrawable::class.java)
    assertThat((drawable as RoundedBitmapDrawable?)?.radii)
        .isEqualTo(floatArrayOf(1f, 1f, 2f, 2f, 3f, 3f, 4f, 4f))
  }

  @Test
  fun testCreateDrawable_whenAlreadyRoundedWithBorder_thenReturnBitmapDrawable() {
    val closeableImage = mock<CloseableStaticBitmap>()
    val bitmap = mock<Bitmap>()
    whenever(closeableImage.underlyingBitmap).thenReturn(bitmap)
    whenever(closeableImage.extras).thenReturn(imageExtrasRounded)
    val options = mock<ImageOptions>()
    val borderOptions = BorderOptions.create(Color.YELLOW, 10f)
    whenever(options.borderOptions).thenReturn(borderOptions)
    whenever(options.roundingOptions)
        .thenReturn(RoundingOptions.asCircle(antiAliasing = false, forceRoundAtDecode = false))
    val drawable = drawableFactory.createDrawable(resources, closeableImage, options)
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(CircularBorderBitmapDrawable::class.java)
    assertThat((drawable as CircularBorderBitmapDrawable?)?.border).isEqualTo(borderOptions)
  }
}


/*
 * 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.fresco.vito.drawable

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.NinePatchDrawable
import android.util.DisplayMetrics
import com.facebook.drawee.drawable.RoundedBitmapDrawable
import com.facebook.drawee.drawable.RoundedColorDrawable
import com.facebook.drawee.drawable.RoundedNinePatchDrawable
import com.facebook.fresco.vito.options.BorderOptions
import com.facebook.fresco.vito.options.RoundingOptions
import java.lang.UnsupportedOperationException
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class RoundingUtilsTest {

  private lateinit var resources: Resources
  private lateinit var displayMetrics: DisplayMetrics

  @Before
  fun setup() {
    resources = mock()
    displayMetrics = DisplayMetrics()
    whenever(resources.displayMetrics).thenReturn(displayMetrics)
  }

  @Test
  fun testRoundedDrawablesWithoutBorder_withBitmap_withNotAlreadyRounded_thenReturnBitmapDrawable() {
    val roundingUtils = RoundingUtils
    val bitmap = mock<Bitmap>()
    val drawable =
        roundingUtils.roundedDrawable(resources, bitmap, null, RoundingOptions.asCircle())
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(RoundedBitmapDrawable::class.java)
    assertThat((drawable as RoundedBitmapDrawable).isCircle).isTrue
  }

  @Test
  fun testRoundedDrawablesWithBorder_withBitmap_withNotAlreadyRounded_thenReturnBitmapDrawable() {
    val roundingUtils = RoundingUtils
    val bitmap = mock<Bitmap>()
    val borderOptions = BorderOptions.create(Color.YELLOW, 10f)
    val drawable =
        roundingUtils.roundedDrawable(resources, bitmap, borderOptions, RoundingOptions.asCircle())
    assertThat(drawable).isNotNull
    assertThat(drawable).isInstanceOf(RoundedBitmapDrawable::class.java)
    assertThat((drawable as RoundedBitmapDrawable).borderWidth).isEqualTo(borderOptions.width)
    assertThat(drawable.borderColor).isEqualTo(borderOptions.color)
    assertThat(drawable.isCircle).isTrue
  }

  @Test
  fun testRoundedDrawablesWithoutBorder_withDrawable_thenReturnBitmapDrawable() {
    val roundingUtils = RoundingUtils
    val roundingOptions = RoundingOptions.asCircle()
    var drawable: Drawable = mock<BitmapDrawable>()
    var result = roundingUtils.roundedDrawable(resources, drawable, null, roundingOptions)
    assertThat(result).isNotNull
    assertThat(result).isInstanceOf(RoundedBitmapDrawable::class.java)
    assertThat((result as RoundedBitmapDrawable).isCircle).isTrue

    drawable = mock<ColorDrawable>()
    result = roundingUtils.roundedDrawable(resources, drawable, null, roundingOptions)
    assertThat(result).isNotNull
    assertThat(result).isInstanceOf(RoundedColorDrawable::class.java)
    assertThat((result as RoundedColorDrawable).isCircle).isTrue

    drawable = mock<NinePatchDrawable>()
    result = roundingUtils.roundedDrawable(resources, drawable, null, roundingOptions)
    assertThat(result).isNotNull
    assertThat(result).isInstanceOf(RoundedNinePatchDrawable::class.java)
    assertThat((result as RoundedNinePatchDrawable).isCircle).isTrue
  }

  @Test
  fun testRoundedDrawablesWithBorder_withDrawable_thenReturnBitmapDrawable() {
    val roundingUtils = RoundingUtils
    var drawable: Drawable = mock<BitmapDrawable>()
    val borderOptions = BorderOptions.create(Color.YELLOW, 10f)
    var result =
        roundingUtils.roundedDrawable(
            resources, drawable, borderOptions, RoundingOptions.asCircle())
    assertThat(result).isNotNull
    assertThat(result).isInstanceOf(RoundedBitmapDrawable::class.java)
    assertThat((result as RoundedBitmapDrawable).borderWidth).isEqualTo(borderOptions.width)
    assertThat(result.borderColor).isEqualTo(borderOptions.color)
    assertThat(result.isCircle).isTrue

    drawable = mock<ColorDrawable>()
    result =
        roundingUtils.roundedDrawable(
            resources, drawable, borderOptions, RoundingOptions.asCircle())
    assertThat(result).isNotNull
    assertThat(result).isInstanceOf(RoundedColorDrawable::class.java)
    assertThat((result as RoundedColorDrawable).borderWidth).isEqualTo(borderOptions.width)
    assertThat(result.borderColor).isEqualTo(borderOptions.color)
    assertThat(result.isCircle).isTrue

    drawable = mock<NinePatchDrawable>()
    result =
        roundingUtils.roundedDrawable(
            resources, drawable, borderOptions, RoundingOptions.asCircle())
    assertThat(result).isNotNull
    assertThat(result).isInstanceOf(RoundedNinePatchDrawable::class.java)
    assertThat((result as RoundedNinePatchDrawable).borderWidth).isEqualTo(borderOptions.width)
    assertThat(result.borderColor).isEqualTo(borderOptions.color)
    assertThat(result.isCircle).isTrue
  }

  @Test(expected = UnsupportedOperationException::class)
  fun testBuildOverlayDrawable_whenInvalidResId_thenThrowNotFoundException() {
    val roundingUtils = RoundingUtils
    val drawable = mock<Drawable>()
    val roundingOptions = mock<RoundingOptions>()
    roundingUtils.roundedDrawable(resources, drawable, null, roundingOptions)
  }
}


/*
 * 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.fresco.vito.core.impl

import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.VitoImagePerfListener

open class BaseVitoImagePerfListener : VitoImagePerfListener {
  override fun onImageMount(drawable: FrescoDrawableInterface) = Unit

  override fun onImageUnmount(drawable: FrescoDrawableInterface) = Unit

  override fun onImageBind(drawable: FrescoDrawableInterface) = Unit

  override fun onImageUnbind(drawable: FrescoDrawableInterface) = Unit

  override fun onImageFetch(drawable: FrescoDrawableInterface) = Unit

  override fun onImageSuccess(drawable: FrescoDrawableInterface, wasImmediate: Boolean) = Unit

  override fun onImageError(drawable: FrescoDrawableInterface) = Unit

  override fun onImageRelease(drawable: FrescoDrawableInterface) = Unit

  override fun onScheduleReleaseDelayed(drawable: FrescoDrawableInterface) = Unit

  override fun onScheduleReleaseNextFrame(drawable: FrescoDrawableInterface) = Unit

  override fun onReleaseImmediately(drawable: FrescoDrawableInterface) = Unit

  override fun onDrawableReconfigured(drawable: FrescoDrawableInterface) = Unit

  override fun onIgnoreResult(drawable: FrescoDrawableInterface) = Unit

  override fun onIgnoreFailure(drawable: FrescoDrawableInterface) = Unit
}


/*
 * 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.fresco.vito.core.impl

import android.graphics.drawable.Drawable
import com.facebook.drawee.backends.pipeline.info.ImageOrigin
import com.facebook.fresco.ui.common.BaseControllerListener2
import com.facebook.fresco.ui.common.ControllerListener2
import com.facebook.fresco.ui.common.ControllerListener2.Extras
import com.facebook.fresco.ui.common.ImagePerfNotifier
import com.facebook.fresco.ui.common.ImagePerfNotifierHolder
import com.facebook.fresco.ui.common.VitoUtils
import com.facebook.fresco.vito.core.CombinedImageListener
import com.facebook.fresco.vito.core.VitoImageRequest
import com.facebook.fresco.vito.core.VitoImageRequestListener
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.imagepipeline.image.ImageInfo
import java.io.Closeable
import java.io.IOException

class CombinedImageListenerImpl : CombinedImageListener {

  private var vitoImageRequestListener: VitoImageRequestListener? = null
  override var imageListener: ImageListener? = null
  private var controllerListener2: ControllerListener2<ImageInfo>? =
      BaseControllerListener2.getNoOpListener()
  private var imagePerfControllerListener: ControllerListener2<ImageInfo>? = null
  private var localImagePerfStateListener: ImagePerfNotifier? = null

  override fun setVitoImageRequestListener(vitoImageRequestListener: VitoImageRequestListener?) {
    this.vitoImageRequestListener = vitoImageRequestListener
  }

  override fun setControllerListener2(controllerListener2: ControllerListener2<ImageInfo>?) {
    this.controllerListener2 = controllerListener2
  }

  override fun setImagePerfControllerListener(
      imagePerfControllerListener: ControllerListener2<ImageInfo>?
  ) {
    this.imagePerfControllerListener = imagePerfControllerListener
    checkAndSetLocalImagePerfStateListener()
  }

  override fun setLocalImagePerfStateListener(imagePerfNotifier: ImagePerfNotifier?) {
    localImagePerfStateListener = imagePerfNotifier
    checkAndSetLocalImagePerfStateListener()
  }

  private fun checkAndSetLocalImagePerfStateListener() {
    val localPerfStatePublisher = imagePerfControllerListener as? ImagePerfNotifierHolder
    if (localImagePerfStateListener != null && localPerfStatePublisher == null) {
      throw NullPointerException(
          "trying to set localImagePerfStateListener without a localPerfStatePublisher")
    }
    localPerfStatePublisher?.setImagePerfNotifier(localImagePerfStateListener)
  }

  override fun onSubmit(
      id: Long,
      imageRequest: VitoImageRequest,
      callerContext: Any?,
      extras: Extras?
  ) {
    vitoImageRequestListener?.onSubmit(id, imageRequest, callerContext, extras)
    imageListener?.onSubmit(id, callerContext)
    val stringId = VitoUtils.getStringId(id)
    controllerListener2?.onSubmit(stringId, callerContext, extras)
    imagePerfControllerListener?.onSubmit(stringId, callerContext, extras)
  }

  override fun onPlaceholderSet(id: Long, imageRequest: VitoImageRequest, placeholder: Drawable?) {
    vitoImageRequestListener?.onPlaceholderSet(id, imageRequest, placeholder)
    imageListener?.onPlaceholderSet(id, placeholder)
  }

  override fun onFinalImageSet(
      id: Long,
      imageRequest: VitoImageRequest,
      @ImageOrigin imageOrigin: Int,
      imageInfo: ImageInfo?,
      extras: Extras?,
      drawable: Drawable?
  ) {
    vitoImageRequestListener?.onFinalImageSet(
        id, imageRequest, imageOrigin, imageInfo, extras, drawable)
    imageListener?.onFinalImageSet(id, imageOrigin, imageInfo, drawable)
    val stringId = VitoUtils.getStringId(id)
    controllerListener2?.onFinalImageSet(stringId, imageInfo, extras)
    imagePerfControllerListener?.onFinalImageSet(stringId, imageInfo, extras)
  }

  override fun onIntermediateImageSet(
      id: Long,
      imageRequest: VitoImageRequest,
      imageInfo: ImageInfo?
  ) {
    vitoImageRequestListener?.onIntermediateImageSet(id, imageRequest, imageInfo)
    imageListener?.onIntermediateImageSet(id, imageInfo)
    val stringId = VitoUtils.getStringId(id)
    controllerListener2?.onIntermediateImageSet(stringId, imageInfo)
    imagePerfControllerListener?.onIntermediateImageSet(stringId, imageInfo)
  }

  override fun onIntermediateImageFailed(
      id: Long,
      imageRequest: VitoImageRequest,
      throwable: Throwable?
  ) {
    vitoImageRequestListener?.onIntermediateImageFailed(id, imageRequest, throwable)
    imageListener?.onIntermediateImageFailed(id, throwable)
    val stringId = VitoUtils.getStringId(id)
    controllerListener2?.onIntermediateImageFailed(stringId)
    imagePerfControllerListener?.onIntermediateImageFailed(stringId)
  }

  override fun onFailure(
      id: Long,
      imageRequest: VitoImageRequest,
      error: Drawable?,
      throwable: Throwable?,
      extras: Extras?
  ) {
    vitoImageRequestListener?.onFailure(id, imageRequest, error, throwable, extras)
    imageListener?.onFailure(id, error, throwable)
    val stringId = VitoUtils.getStringId(id)
    controllerListener2?.onFailure(stringId, throwable, extras)
    imagePerfControllerListener?.onFailure(stringId, throwable, extras)
  }

  override fun onRelease(id: Long, imageRequest: VitoImageRequest, extras: Extras?) {
    vitoImageRequestListener?.onRelease(id, imageRequest, extras)
    imageListener?.onRelease(id)
    val stringId = VitoUtils.getStringId(id)
    controllerListener2?.onRelease(stringId, extras)
    imagePerfControllerListener?.onRelease(stringId, extras)
  }

  override fun onEmptyEvent(callerContext: Any?) {
    vitoImageRequestListener?.onEmptyEvent(callerContext)
    controllerListener2?.onEmptyEvent(callerContext)
    imagePerfControllerListener?.onEmptyEvent(callerContext)
  }

  override fun onReset() {
    try {
      (imagePerfControllerListener as? Closeable)?.close()
    } catch (e: IOException) {}
  }
}