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

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

class CanvasTransformationHandler(var canvasTransformation: CanvasTransformation? = null) {

  private val tempMatrix = Matrix()

  private var drawMatrix: Matrix? = null

  fun getMatrix(): Matrix? = drawMatrix

  fun configure(bounds: Rect, childWidth: Int, childHeight: Int) {
    // We only scale the model if its dimensions are > 0
    if (childWidth <= 0 || childHeight <= 0) {
      drawMatrix = null
      return
    }
    drawMatrix =
        canvasTransformation?.calculateTransformation(tempMatrix, bounds, childWidth, childHeight)
  }
}


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

import android.graphics.Bitmap
import android.graphics.Paint
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.graphics.drawable.NinePatchDrawable

sealed class ImageDataModel {
  open val width = -1
  open val height = -1
  open val defaultPaintFlags = Paint.ANTI_ALIAS_FLAG

  open fun setCallback(callback: Drawable.Callback?) = Unit

  open fun onAttach() = Unit

  open fun onDetach() = Unit
}

class ColorIntImageDataModel(val colorInt: Int) : ImageDataModel()

class BitmapImageDataModel(val bitmap: Bitmap, val isBitmapCircular: Boolean = false) :
    ImageDataModel() {
  override val width = bitmap.width
  override val height = bitmap.height
  override val defaultPaintFlags: Int = Paint.FILTER_BITMAP_FLAG or Paint.DITHER_FLAG
}

open class DrawableImageDataModel(val drawable: Drawable) : ImageDataModel() {
  override val width = if (drawable is NinePatchDrawable) -1 else drawable.intrinsicWidth
  override val height = if (drawable is NinePatchDrawable) -1 else drawable.intrinsicHeight

  override fun setCallback(callback: Drawable.Callback?) {
    drawable.callback = callback
  }
}

class AnimatedDrawableImageDataModel(
    drawable: Drawable,
    val animatable: Animatable,
    val isAutoPlay: Boolean
) : DrawableImageDataModel(drawable) {
  override fun onAttach() {
    if (isAutoPlay) {
      animatable.start()
    }
  }

  override fun onDetach() {
    // We only update if we started the animation
    if (isAutoPlay) {
      animatable.stop()
    }
  }
}


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

import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Shader
import com.facebook.fresco.vito.renderer.util.ColorUtils

typealias RenderCommand = (Canvas) -> Unit

object ImageRenderer {

  /**
   * Creates an image data model render command.
   *
   * NOTE: The render command can modify the Canvas that it draws on and does not restore the
   * canvas. The Canvas has to be saved & restored manually if required.
   */
  fun createImageDataModelRenderCommand(
      model: ImageDataModel,
      shape: Shape,
      paint: Paint,
      imageTransformation: Matrix? = null
  ): RenderCommand {
    return when (model) {
      is BitmapImageDataModel -> model.createRenderCommand(shape, paint, imageTransformation)
      is ColorIntImageDataModel -> model.createRenderCommand(shape, paint)
      is DrawableImageDataModel -> model.createRenderCommand(shape, paint, imageTransformation)
    }
  }

  inline fun BitmapImageDataModel.createRenderCommand(
      shape: Shape,
      paint: Paint,
      imageTransformation: Matrix? = null
  ): RenderCommand =
      when (shape) {
        is RectShape -> bitmapRenderCommand(paint, bitmap, imageTransformation)
        is CircleShape ->
            if (!isBitmapCircular) {
              paintRenderCommand(shape, paint.setBitmap(bitmap, imageTransformation))
            } else {
              bitmapRenderCommand(paint, bitmap, imageTransformation)
            }
        else -> {
          paintRenderCommand(shape, paint.setBitmap(bitmap, imageTransformation))
        }
      }

  inline fun ColorIntImageDataModel.createRenderCommand(shape: Shape, paint: Paint): RenderCommand {
    // The image transformation is a no-op for solid colors since it remains a solid color
    paint.color = ColorUtils.multiplyColorAlpha(colorInt, paint.alpha)
    return paintRenderCommand(shape, paint)
  }

  inline fun DrawableImageDataModel.createRenderCommand(
      shape: Shape,
      paint: Paint,
      imageTransformation: Matrix? = null,
  ): RenderCommand {
    // We transform by scaling the Canvas, so we let the Drawable draw itself with its
    // preferred dimensions
    when (shape) {
      is RectShape -> return { canvas ->
            if (width >= 0 && height >= 0) {
              drawable.setBounds(0, 0, width, height)
              canvas.concat(imageTransformation)
            } else {
              // The image dimensions are not set, so we assume the image can stretch to fit the
              // entire rect, so we do not apply the transformation.
              drawable.setBounds(
                  shape.rect.left.toInt(),
                  shape.rect.top.toInt(),
                  shape.rect.right.toInt(),
                  shape.rect.bottom.toInt())
            }
            drawable.colorFilter = paint.colorFilter
            drawable.alpha = paint.alpha
            drawable.draw(canvas)
          }
      else -> {
        return { canvas ->
          drawable.setBounds(0, 0, width, height)
          drawable.colorFilter = null // The Paint handles the color filter
          val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
          drawable.draw(Canvas(bitmap))
          paint.setBitmap(bitmap, imageTransformation)
          shape.draw(canvas, paint)
        }
      }
    }
  }

  inline fun bitmapRenderCommand(
      paint: Paint,
      bitmap: Bitmap,
      imageTransformation: Matrix?
  ): RenderCommand = { canvas ->
    canvas.concat(imageTransformation)
    canvas.drawBitmap(bitmap, 0f, 0f, paint)
  }

  inline fun paintRenderCommand(shape: Shape, paint: Paint): RenderCommand = { canvas ->
    shape.draw(canvas, paint)
  }

  inline fun Paint.setBitmap(
      bitmap: Bitmap,
      shaderTransformation: Matrix? = null,
  ): Paint {
    shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    shader.setLocalMatrix(shaderTransformation)
    return this
  }
}


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

import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF

sealed class Shape {
  abstract fun draw(canvas: Canvas, paint: Paint)
}

class RectShape(val rect: RectF) : Shape() {
  override fun draw(canvas: Canvas, paint: Paint) = canvas.drawRect(rect, paint)
}

class CircleShape(val cx: Float, val cy: Float, val radius: Float) : Shape() {
  override fun draw(canvas: Canvas, paint: Paint) = canvas.drawCircle(cx, cy, radius, paint)
}

class RoundedRectShape(val rect: RectF, val rx: Float, val ry: Float) : Shape() {
  override fun draw(canvas: Canvas, paint: Paint) = canvas.drawRoundRect(rect, rx, ry, paint)
}

class PathShape(val path: Path) : Shape() {
  override fun draw(canvas: Canvas, paint: Paint) = canvas.drawPath(path, paint)
}


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

class ColorUtils {
  companion object {

    /**
     * Applies the given alpha to the color.
     *
     * @param color to add alpha
     * @param alpha to adjust opacity
     */
    fun multiplyColorAlpha(color: Int, alpha: Int): Int {
      return when (alpha) {
        255 -> color
        0 -> color and 0x00FFFFFF
        else -> {
          val cappedAlpha = alpha + (alpha shr 7) // make it 0..256
          val colorAlpha = color ushr 24
          val multipliedAlpha = colorAlpha * cappedAlpha shr 8
          multipliedAlpha shl 24 or (color and 0x00FFFFFF)
        }
      }
    }
  }
}


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

import android.graphics.Bitmap

data class BitmapImageSource(val bitmap: Bitmap) : ImageSource


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

object EmptyImageSource : ImageSource


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

data class FirstAvailableImageSource(val imageSources: Array<out ImageSource>) : ImageSource


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

/**
 * Image source that can be passed to Fresco's image components. New image sources can be created
 * via [ImageSourceProvider].
 */
interface ImageSource


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

object ImageSourceExtras {
  // Optional image format hint
  const val IMAGE_FORMAT = "image_format"
}


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

import android.graphics.Bitmap
import android.net.Uri
import com.facebook.fresco.middleware.HasExtraData

/**
 * Create image sources that can be passed to Fresco's image components. For example, to create a
 * single image source for a given URI, call [forUri(Uri)] or [forUri(String)].
 */
object ImageSourceProvider {

  /**
   * Uri parsing function from String to URI. The default implementation can be replaced with any
   * custom URI parsing logic if required.
   */
  var uriParser: (String) -> Uri = {
    val uri: Uri = Uri.parse(it)
    uri
  }

  /** @return an empty image source if no image URI is available to pass to the UI component */
  @JvmStatic fun emptySource(): ImageSource = EmptyImageSource

  /**
   * Create a single image source for a given image URI.
   *
   * @param uri the image URI to use
   * @return the ImageSource to be passed to the UI component
   */
  @JvmOverloads
  @JvmStatic
  fun forUri(uri: Uri?, extras: Map<String, Any>? = null): ImageSource =
      if (uri == null) {
        emptySource()
      } else {
        SingleImageSource(
            uri,
            buildMap {
              if (extras != null) {
                putAll(extras)
              }
              put(HasExtraData.KEY_URI_SOURCE, uri)
            })
      }

  /**
   * Create a single image source for a given image URI.
   *
   * @param uriString the image URI String to use
   * @return the ImageSource to be passed to the UI component
   */
  @JvmOverloads
  @JvmStatic
  fun forUri(uriString: String?, extras: Map<String, Any>? = null): ImageSource {
    val uri: Uri? = uriString?.let { uriParser(it) }
    return forUri(uri, extras)
  }

  /**
   * Create a multi image source for a given set of sources. Image sources are obtained in order.
   * Only if the current source fails, or if it finishes without a result, the next one will be
   * tried.
   *
   * @param imageSources the list of image sources to be used
   * @return the ImageSource to be passed to the UI component
   */
  @JvmStatic
  fun firstAvailable(vararg imageSources: ImageSource): ImageSource =
      FirstAvailableImageSource(imageSources)

  /**
   * Create a multi image source for a low- and high resolution image. Both requests will be sent
   * off, the low resolution will be used as an intermediate image until the high resolution one is
   * available.
   *
   * @param lowResImageSource the low resolution image source to be used
   * @param highResImageSource the high resolution image source to be used
   * @return the ImageSource to be passed to the UI component
   */
  @JvmStatic
  fun increasingQuality(
      lowResImageSource: ImageSource,
      highResImageSource: ImageSource
  ): ImageSource = IncreasingQualityImageSource(lowResImageSource, highResImageSource)

  /**
   * Create a multi image source for a low- and high resolution image. Both requests will be sent
   * off, the low resolution will be used as an intermediate image until the high resolution one is
   * available.
   *
   * @param lowResImageUri the low resolution image URI to be used
   * @param highResImageUri the high resolution image URI to be used
   * @return the ImageSource to be passed to the UI component
   */
  @JvmStatic
  fun increasingQuality(lowResImageUri: Uri?, highResImageUri: Uri?): ImageSource =
      if (lowResImageUri == null) {
        forUri(highResImageUri)
      } else IncreasingQualityImageSource(forUri(lowResImageUri), forUri(highResImageUri))

  /**
   * Create a bitmap image source for a given bitmap image buffer.
   *
   * @param bitmap the bitmap image buffer to be used
   * @return the ImageSource be be passed to the UI component
   */
  @JvmStatic fun bitmap(bitmap: Bitmap): ImageSource = BitmapImageSource(bitmap)
}


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

package com.facebook.fresco.vito.source

data class IncreasingQualityImageSource(
    val lowResSource: ImageSource,
    val highResSource: ImageSource
) : ImageSource


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

import android.net.Uri

data class SingleImageSource(val uri: Uri, val extras: Map<String, Any>? = null) : ImageSource {

  fun getExtra(key: String): Any? = extras?.get(key)

  fun getStringExtra(key: String): String? = getExtra(key) as? 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.textspan

import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.os.SystemClock
import android.text.style.ReplacementSpan
import android.view.View
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.widget.text.span.BetterImageSpan

open class VitoSpan(
    val resources: Resources,
    val drawableInterface: FrescoDrawableInterface,
    @BetterImageSpan.BetterImageSpanAlignment
    verticalAlignment: Int = BetterImageSpan.ALIGN_BASELINE,
    private val imageSpan: BetterImageSpan =
        BetterImageSpan(drawableInterface as Drawable, verticalAlignment)
) : ReplacementSpan(), Drawable.Callback {

  var parentView: View? = null

  var imageFetchCommand: (() -> Boolean)? = null

  init {
    (drawableInterface as Drawable).callback = this
  }

  override fun getSize(
      paint: Paint,
      text: CharSequence?,
      start: Int,
      end: Int,
      fm: Paint.FontMetricsInt?
  ): Int {
    return imageSpan.getSize(paint, text, start, end, fm)
  }

  override fun draw(
      canvas: Canvas,
      text: CharSequence?,
      start: Int,
      end: Int,
      x: Float,
      top: Int,
      y: Int,
      bottom: Int,
      paint: Paint
  ) {
    imageSpan.draw(canvas, text, start, end, x, top, y, bottom, paint)
  }

  override fun invalidateDrawable(who: Drawable) {
    parentView?.invalidate()
  }

  override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
    parentView?.apply {
      // 'parentView.scheduleDrawable(who, what, when)' wouldn't work because
      // it cannot determine the 'who' drawable with 'verifyDrawable(who)'.
      // So we're re-implementing 'scheduleDrawable' manually.
      val delay = `when` - SystemClock.uptimeMillis()
      postDelayed(what, delay)
    }
  }

  override fun unscheduleDrawable(who: Drawable, what: Runnable) {
    parentView?.removeCallbacks(what)
  }
}


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

import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.view.View
import com.facebook.common.callercontext.ContextChain
import com.facebook.common.lifecycle.AttachDetachListener
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.VitoImageRequest
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.widget.text.span.BetterImageSpan

/** Vito text span image loader implementation */
object VitoSpanLoader {

  @JvmStatic
  @JvmOverloads
  fun createSpan(
      resources: Resources,
      @BetterImageSpan.BetterImageSpanAlignment
      verticalAlignment: Int = BetterImageSpan.ALIGN_BASELINE
  ): VitoSpan = VitoSpan(resources, createDrawable(), verticalAlignment)

  @JvmStatic
  fun show(
      imageSource: ImageSource,
      imageOptions: ImageOptions,
      callerContext: Any?,
      contextChain: ContextChain?,
      imageListener: ImageListener?,
      target: VitoSpan
  ) {
    show(
        FrescoVitoProvider.getImagePipeline()
            .createImageRequest(target.resources, imageSource, imageOptions),
        callerContext,
        contextChain,
        imageListener,
        target)
  }

  @JvmStatic
  fun show(
      imageRequest: VitoImageRequest,
      callerContext: Any?,
      contextChain: ContextChain?,
      imageListener: ImageListener?,
      target: VitoSpan
  ) {
    val fetchCommand = {
      FrescoVitoProvider.getController()
          .fetch(
              frescoDrawable = target.drawableInterface,
              imageRequest = imageRequest,
              callerContext = callerContext,
              contextChain = contextChain,
              listener = imageListener,
              onFadeListener = null,
              viewportDimensions = null)
    }
    target.imageFetchCommand = fetchCommand
    fetchCommand()
  }

  @JvmStatic
  fun release(target: VitoSpan?) {
    target?.let { FrescoVitoProvider.getController().releaseImmediately(target.drawableInterface) }
  }

  @JvmStatic
  fun setImageSpanOnBuilder(
      sb: SpannableStringBuilder,
      imageSpan: VitoSpan,
      startIndex: Int,
      endIndex: Int,
      imageWidthPx: Int,
      imageHeightPx: Int,
      parentView: View?
  ) {
    if (endIndex > sb.length) {
      // Unfortunately, some callers use this wrong. The original implementation also swallows
      // an exception if this happens (e.g. if you tap on a video that has a minutiae as well.
      // Example: Text = "ABC", insert image at position 18.
      return
    }
    (imageSpan.drawableInterface as Drawable).setBounds(0, 0, imageWidthPx, imageHeightPx)
    imageSpan.parentView = parentView

    sb.setSpan(imageSpan, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
  }

  fun SpannableStringBuilder.setImageSpan(
      imageSpan: VitoSpan,
      startIndex: Int,
      endIndex: Int,
      imageWidthPx: Int,
      imageHeightPx: Int,
      parentView: View?
  ) {
    setImageSpanOnBuilder(
        this, imageSpan, startIndex, endIndex, imageWidthPx, imageHeightPx, parentView)
  }

  fun createDrawable(): FrescoDrawableInterface =
      FrescoVitoProvider.getController().createDrawable()

  class VitoAttachDetachListener(val vitoSpan: VitoSpan) : AttachDetachListener {
    override fun onAttachToView(view: View) {
      vitoSpan.parentView = view
      vitoSpan.imageFetchCommand?.let { it() }
    }

    override fun onDetachFromView(view: View) {
      vitoSpan.parentView = null
      release(vitoSpan)
    }
  }
}


/*
 * 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.tools.liveeditor

import android.content.Context
import com.facebook.fresco.vito.core.FrescoController2
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.VitoImagePipeline
import com.facebook.fresco.vito.core.VitoImageRequest
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.fresco.vito.source.ImageSourceProvider

class ImageLiveEditor(
    val drawable: FrescoDrawableInterface,
    private val imagePipeline: VitoImagePipeline,
    private val frescoController: FrescoController2
) {

  private var originalRequest = drawable.imageRequest

  fun editOptions(context: Context, function: (ImageOptions.Builder) -> Unit) {
    val builder = getOptions().extend()
    function.invoke(builder)
    fetch(context = context, imageOptions = builder.build())
  }

  fun editSource(context: Context, function: (ImageSource) -> ImageSource) {
    fetch(context = context, imageSource = function.invoke(getSource()))
  }

  fun fetch(
      context: Context,
      imageSource: ImageSource = getSource(),
      imageOptions: ImageOptions = getOptions()
  ) {
    if (originalRequest == null) {
      originalRequest = drawable.imageRequest
    }
    val newRequest = imagePipeline.createImageRequest(context.resources, imageSource, imageOptions)
    fetch(newRequest)
  }

  fun fetch(request: VitoImageRequest) {
    frescoController.fetch(
        frescoDrawable = drawable,
        imageRequest = request,
        callerContext = drawable.callerContext,
        contextChain = null,
        listener = drawable.imageListener,
        onFadeListener = null,
        viewportDimensions = null)
  }

  fun getOptions(): ImageOptions = drawable.imageRequest?.imageOptions ?: ImageOptions.defaults()

  fun getSource(): ImageSource =
      drawable.imageRequest?.imageSource ?: ImageSourceProvider.emptySource()

  fun getOriginalSource(): ImageSource =
      originalRequest?.imageSource ?: ImageSourceProvider.emptySource()

  fun reset() = originalRequest?.let(::fetch)
}


/*
 * 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.tools.liveeditor

import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.PointF
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Build
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.fresco.vito.options.BorderOptions
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.options.RoundingOptions

object ImageOptionsSampleValues {

  data class Entry<T>(
      val name: String,
      val data: List<Pair<String, T>>,
      val updateFunction: (ImageOptions.Builder, T) -> ImageOptions.Builder
  )

  val roundingOptions =
      Entry(
          "Rounding",
          listOf(
              "none" to null,
              "as circle" to RoundingOptions.asCircle(),
              "corner radius" to RoundingOptions.forCornerRadiusPx(20f),
              "different radii" to RoundingOptions.forCornerRadii(0f, 20f, 30f, 40f))) { b, v ->
            b.round(v)
          }

  val borderOptions =
      Entry(
          "Border",
          listOf(
              "none" to null,
              "red border" to BorderOptions.create(Color.RED, 20f),
              "blue border" to BorderOptions.create(Color.BLUE, 40f),
              "border with no padding" to BorderOptions.create(Color.GREEN, 20f, 0f),
              "border with small padding" to BorderOptions.create(Color.GREEN, 20f, 10f),
              "border with same padding" to BorderOptions.create(Color.GREEN, 20f, 20f),
              "border with more padding" to BorderOptions.create(Color.GREEN, 20f, 40f))) { b, v ->
            b.borders(v)
          }

  val scaleTypes =
      Entry(
          "Scale type",
          listOf(
              "center" to Pair(ScalingUtils.ScaleType.CENTER, null),
              "center_crop" to Pair(ScalingUtils.ScaleType.CENTER_CROP, null),
              "center_inside" to Pair(ScalingUtils.ScaleType.CENTER_INSIDE, null),
              "fit_center" to Pair(ScalingUtils.ScaleType.FIT_CENTER, null),
              "fit_start" to Pair(ScalingUtils.ScaleType.FIT_START, null),
              "fit_end" to Pair(ScalingUtils.ScaleType.FIT_END, null),
              "fit_xy" to Pair(ScalingUtils.ScaleType.FIT_XY, null),
              "focus_crop (0, 0)" to Pair(ScalingUtils.ScaleType.FOCUS_CROP, PointF(0f, 0f)),
              "focus_crop (1, 0.5)" to Pair(ScalingUtils.ScaleType.FOCUS_CROP, PointF(1f, 0.5f)),
              "null" to Pair(null, null))) { b, v ->
            b.scale(v.first).focusPoint(v.second)
          }

  val colorFilters =
      Entry(
          "Color filter",
          listOf(
              "none" to PorterDuffColorFilter(Color.TRANSPARENT, PorterDuff.Mode.ADD),
              "red" to PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN),
              "green" to PorterDuffColorFilter(Color.GREEN, PorterDuff.Mode.DARKEN),
              "blue" to PorterDuffColorFilter(Color.BLUE, PorterDuff.Mode.DARKEN),
              "tint: black" to PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_ATOP),
              "tint: white" to PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP),
              "tint: gray" to PorterDuffColorFilter(Color.GRAY, PorterDuff.Mode.SRC_ATOP),
              "tint: red" to PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP),
              "tint: green" to PorterDuffColorFilter(Color.GREEN, PorterDuff.Mode.SRC_ATOP),
              "tint: blue" to PorterDuffColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP))) { b, v
            ->
            b.colorFilter(v)
          }

  val fadingOptions =
      Entry(
          "Fading",
          listOf(
              "none" to 0,
              "50 ms" to 50,
              "100 ms" to 100,
              "200 ms" to 200,
              "500 ms" to 500,
              "1000 ms" to 1000,
              "2000 ms" to 2000,
              "5000 ms" to 5000,
              "10000 ms" to 10000)) { b, v ->
            b.fadeDurationMs(v)
          }

  val autoPlay = Entry("Autoplay", listOf("off" to false, "on" to true)) { b, v -> b.autoPlay(v) }

  val bitmapConfig =
      Entry(
          "Bitmap config",
          listOfNotNull(
              "ARGB 8888" to Bitmap.Config.ARGB_8888,
              "RGB 565" to Bitmap.Config.RGB_565,
              "ALPHA 8" to Bitmap.Config.ALPHA_8,
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                  "RGBA F16" to Bitmap.Config.RGBA_F16
              else null,
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                  "HARDWARE" to Bitmap.Config.HARDWARE
              else null)) { b, v ->
            b.bitmapConfig(v)
          }

  val resizeToViewportConfig =
      Entry("Resize to viewport", listOf("off" to false, "on" to true)) { b, v ->
        b.resizeToViewport(v)
      }

  val localThumbnailPreviewsEnabledConfig =
      Entry("Local thumbnail preview", listOf("off" to false, "on" to true)) { b, v ->
        b.localThumbnailPreviewsEnabled(v)
      }
  val progressiveRenderingEnabledConfig =
      Entry("Progressive rendering", listOf("off" to false, "on" to true)) { b, v ->
        b.progressiveRendering(v)
      }

  val placeholderColors =
      Entry(
          "Placeholder color",
          listOf(
              "Black" to Color.BLACK,
              "Dark Gray" to Color.DKGRAY,
              "Gray" to Color.GRAY,
              "Light Gray" to Color.LTGRAY,
              "White" to Color.WHITE,
              "Red" to Color.RED,
              "Green" to Color.GREEN,
              "Blue" to Color.BLUE,
              "Yellow" to Color.YELLOW,
              "Cyan" to Color.CYAN,
              "Magenta" to Color.MAGENTA,
              "Transparent" to Color.TRANSPARENT,
          )) { builder, colorInt ->
            builder.placeholderColor(colorInt)
          }

  fun <T> nameForValue(data: List<Pair<String, T?>>, value: T?): String {
    return data.find { value == it.second }?.first ?: "unknown"
  }
}


/*
 * 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.tools.liveeditor

import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.ShapeDrawable
import com.facebook.fresco.vito.core.FrescoController2
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.VitoImagePipeline
import com.facebook.fresco.vito.tools.liveeditor.LiveEditorUiUtils.Companion.dpToPx

class ImageSelector(
    val tracker: ImageTracker,
    private val imagePipeline: VitoImagePipeline,
    private val controller: FrescoController2,
    private val overlayColor: Int = Color.GREEN,
    private var currentIndex: Int = -1
) {

  var currentEditor: ImageLiveEditor? = null

  fun selectNext(context: Context) = highlightDrawable(context, incrementIndexBy(1))

  fun selectPrevious(context: Context) = highlightDrawable(context, incrementIndexBy(-1))

  fun highlightDrawable(context: Context, drawable: FrescoDrawableInterface?) {

    removeHighlight(context)

    if (drawable == null) {
      return
    }

    val editor = ImageLiveEditor(drawable, imagePipeline, controller)

    currentEditor = editor

    val overlayColorDrawable =
        ShapeDrawable().apply {
          paint?.style = Paint.Style.STROKE
          paint?.color = overlayColor
          paint?.strokeWidth = 10.dpToPx(context).toFloat()
        }

    editor.editOptions(context) { it.overlay(overlayColorDrawable) }
  }

  fun removeHighlight(context: Context) = currentEditor?.editOptions(context) { it.overlay(null) }

  private fun highlightDrawable(context: Context, index: Int?) {
    if (index != null) {
      highlightDrawable(context, tracker.getDrawableOrNull(index))
    }
  }

  private fun incrementIndexBy(increment: Int): Int? {
    val size = tracker.drawableCount
    if (size <= 0) {
      return null
    }
    currentIndex = (currentIndex + increment).mod(size)
    return currentIndex
  }
}


/*
 * 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.tools.liveeditor

import com.facebook.fresco.vito.source.ImageSource
import kotlin.jvm.Throws

object ImageSourceParser {

  private const val HIERARCHY_TOKEN = "::"
  private const val OPEN_TOKEN = '{'
  private const val CLOSE_TOKEN = '}'
  private const val SEPARATOR_TOKEN = ','
  private const val ASSIGN_TOKEN = '='
  private const val WHITESPACE = ' '

  fun convertSourceToKeyValue(imageSourceStr: String?): List<Pair<String, String>> {
    if (imageSourceStr == null) {
      return listOf()
    }

    val keyValues = mutableListOf<Pair<String, String>>()

    try {
      getKeyValuesFromString(imageSourceStr, keyValues)
    } catch (exception: ImageSourceSyntaxException) {
      exception.printStackTrace()
      return emptyList()
    }

    return keyValues
  }

  @Throws(ImageSourceSyntaxException::class)
  private fun getKeyValuesFromString(source: String, keyValues: MutableList<Pair<String, String>>) {

    var index = 0
    val key = StringBuilder()
    val value = StringBuilder()
    var parentKey: String? = null

    val throwable =
        ImageSourceSyntaxException(
            "source is not a valid representation for instance ${ImageSource::class.java.name}")

    outer@ while (index < source.length) {

      when (source[index]) {
        OPEN_TOKEN,
        SEPARATOR_TOKEN -> {

          ++index

          if (key.isNotBlank() && value.isEmpty()) {
            parentKey = key.toString()
            addPairAndClearBuffer(keyValues, key, value)
            continue
          }
          while (index < source.length && source[index] != ASSIGN_TOKEN) {
            if (source[index].isWhitespace()) {
              ++index
            } else if (source[index] == OPEN_TOKEN) {
              val parentType = parentKey
              val parentField = key.toString().trimStart()

              parentKey = value.toString()

              if (parentType.isNullOrEmpty()) {
                throw throwable
              }

              key.clear()
              key.append("$parentType$HIERARCHY_TOKEN$parentField")

              addPairAndClearBuffer(keyValues, key, value)

              key.append('\n')

              continue@outer
            } else {
              key.append(source[index++])
            }
          }
        }
        ASSIGN_TOKEN -> {
          ++index
          while (index < source.length &&
              source[index] != SEPARATOR_TOKEN &&
              source[index] != CLOSE_TOKEN) {

            if (source[index] == WHITESPACE && value.isEmpty()) {
              ++index
            } else if (source[index] == OPEN_TOKEN) {

              val parentType = parentKey
              val parentField = key.toString().trimStart()

              parentKey = value.toString()

              if (parentType.isNullOrEmpty()) {
                throw throwable
              }

              key.clear()
              key.append("$parentType$HIERARCHY_TOKEN$parentField")

              addPairAndClearBuffer(keyValues, key, value)

              continue@outer
            } else {
              value.append(source[index++])
            }
          }
          addPairAndClearBuffer(keyValues, key, value)
        }
        WHITESPACE,
        CLOSE_TOKEN -> {
          ++index
        }
        else -> {
          key.append(source[index++])
        }
      }
    }
  }

  private fun addPairAndClearBuffer(
      keyValues: MutableList<Pair<String, String>>,
      key: StringBuilder,
      value: StringBuilder
  ) {
    if (key.isEmpty() && value.isEmpty()) {
      return
    }

    keyValues.add(Pair(key.toString(), value.toString()))

    key.clear()
    value.clear()
  }
}


/*
 * 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.tools.liveeditor

import com.facebook.fresco.vito.source.ImageSource
import com.facebook.fresco.vito.source.ImageSourceProvider

object ImageSourceSampleValues {
  data class Entry<T>(
      val name: String,
      val data: List<Pair<String, T>>,
      val updateFunction: (ImageSource, T) -> ImageSource
  )

  val entries =
      Entry(
          "Image Source",
          listOf(
              Pair(
                  "JPEG",
                  ImageSourceProvider.forUri(
                      "https://frescolib.org/static/sample-images/animal_a_l.jpg")),
              Pair(
                  "PNG",
                  ImageSourceProvider.forUri(
                      "https://frescolib.org/static/sample-images/animal_b.png")),
              Pair(
                  "WebP",
                  ImageSourceProvider.forUri("https://www.gstatic.com/webp/gallery/2.webp")),
              Pair(
                  "Animated WebP",
                  ImageSourceProvider.forUri("https://www.gstatic.com/webp/animated/1.webp")),
              Pair(
                  "GIF",
                  ImageSourceProvider.forUri(
                      "https://media2.giphy.com/media/3oge84qhopFbFFkwec/giphy.gif")),
              Pair(
                  "non-existing",
                  ImageSourceProvider.forUri(
                      "https://frescolib.org/static/sample-images/does_not_exist.jpg")))) { _, s ->
            s
          }
}


// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.

package com.facebook.fresco.vito.tools.liveeditor

class ImageSourceSyntaxException(message: String?) : Exception(message)


/*
 * 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.tools.liveeditor

import android.app.AlertDialog
import android.content.Context
import android.graphics.Typeface
import android.text.Spannable
import android.text.SpannableString
import android.text.style.StyleSpan
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast

class ImageSourceUiUtil(private val context: Context) {

  private fun createView(): ViewGroup {
    return LinearLayout(context).apply { addView(createTextView()) }
  }

  private fun createTextView(): TextView {
    return TextView(context).apply {
      id = android.R.id.text1
      textSize = 12.spToPx(context)
    }
  }

  fun createSourceDialog(source: List<Pair<String, String>>): AlertDialog? {

    if (source.isEmpty()) {
      Toast.makeText(context, "Source is Empty", Toast.LENGTH_LONG).show()
      return null
    }

    val items =
        source.map {
          val spannable = SpannableString("${it.first} \n${it.second}")
          spannable.apply {
            setSpan(
                StyleSpan(Typeface.BOLD), 0, it.first.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
            setSpan(
                StyleSpan(Typeface.ITALIC),
                it.first.length,
                spannable.length,
                Spannable.SPAN_INCLUSIVE_INCLUSIVE)
          }
        }

    val dialogBuilder = AlertDialog.Builder(context)
    val layout = createView()
    var dialog: AlertDialog? =
        dialogBuilder
            .setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, items), null)
            .create()
    dialog?.setView(layout)
    dialog?.setOnDismissListener {
      items.forEach {
        val spans = it.getSpans(0, it.length, StyleSpan::class.java)
        for (span in spans) {
          it.removeSpan(span)
        }
      }
      dialog = null
    }

    return dialog
  }

  fun createImageInfoView(info: Pair<String, String>, parent: ViewGroup): View {
    val spannable = SpannableString("${info.first} \n${info.second}")
    spannable.apply {
      setSpan(StyleSpan(Typeface.BOLD), 0, info.first.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
      setSpan(
          StyleSpan(Typeface.ITALIC),
          info.first.length,
          spannable.length,
          Spannable.SPAN_INCLUSIVE_INCLUSIVE)
    }

    val textView: TextView =
        LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false)
            as TextView
    return textView.apply { text = spannable }
  }

  private fun Int.spToPx(context: Context): Float =
      TypedValue.applyDimension(
          TypedValue.COMPLEX_UNIT_SP, this.toFloat(), context.resources.displayMetrics)
}


/*
 * 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.tools.liveeditor

import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.impl.BaseVitoImagePerfListener
import java.lang.ref.WeakReference

open class ImageTracker : BaseVitoImagePerfListener() {
  private val drawables: MutableList<WeakReference<FrescoDrawableInterface>> = ArrayList()

  val drawableCount: Int
    get() = drawables.size

  fun getDrawableOrNull(index: Int): FrescoDrawableInterface? = drawables.getOrNull(index)?.get()

  fun reset() = drawables.clear()

  private fun trackDrawable(drawable: FrescoDrawableInterface) {
    drawables.add(WeakReference(drawable))
  }

  private fun removeDrawable(drawable: FrescoDrawableInterface) {
    // Remove the Drawable and any null drawables that have been collected (weak reference)
    drawables.removeAll { it.get() == drawable || it.get() == null }
  }

  override fun onImageMount(drawable: FrescoDrawableInterface) = trackDrawable(drawable)

  override fun onImageUnmount(drawable: FrescoDrawableInterface) = removeDrawable(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.tools.liveeditor

import android.content.Context
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.hardware.display.DisplayManager
import android.os.Build
import android.provider.Settings
import android.util.DisplayMetrics
import android.view.Display.DEFAULT_DISPLAY
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.annotation.ColorInt
import com.facebook.common.internal.Supplier
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.impl.StringDebugDataProvider
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.tools.liveeditor.ImageOptionsSampleValues.Entry
import com.facebook.fresco.vito.tools.liveeditor.LiveEditorUiUtils.Companion.dpToPx
import com.facebook.fresco.vito.tools.liveeditor.LiveEditorUiUtils.Companion.dpToPxF

class LiveEditorOnScreenButtonController(
    private val isEnabled: Supplier<Boolean>,
    @ColorInt private val buttonTextColor: Int = Color.WHITE,
    @ColorInt private val buttonBackgroundColor: Int = Color.BLUE,
    @ColorInt private val editorBackgroundColor: Int = Color.WHITE,
    additionalButtonConfig: ButtonConfig? = null,
    private val customOptions: CustomOptions,
    private val debugDataProviders: List<StringDebugDataProvider> = emptyList(),
    private val showOverlayPermissionMessage: ((Context) -> Unit) = {
      Toast.makeText(
              it,
              "In order to use Image Live Editing, you must allow your app to 'Display over other apps' via Android settings",
              Toast.LENGTH_LONG)
          .show()
    }
) {

  /** Called from fblite java code */
  constructor(
      isEnabled: Supplier<Boolean>,
      @ColorInt buttonTextColor: Int = Color.WHITE,
      @ColorInt buttonBackgroundColor: Int = Color.BLUE,
      @ColorInt editorBackgroundColor: Int = Color.WHITE,
      additionalButtonConfig: ButtonConfig? = null
  ) : this(
      isEnabled,
      buttonTextColor,
      buttonBackgroundColor,
      editorBackgroundColor,
      additionalButtonConfig,
      CustomOptions(emptyList()))

  class ButtonConfig(val title: String, val action: View.OnClickListener)

  class CustomOptions(val entries: List<Entry<out Any>>)

  private val buttons =
      listOfNotNull(
          ButtonConfig("Prev img") { imageSelector?.selectPrevious(it.context) },
          ButtonConfig("Next img") { imageSelector?.selectNext(it.context) },
          ButtonConfig("Edit img") { showLiveEditor(it.context) },
          ButtonConfig("Info") { showImageInfo(it.context) },
          additionalButtonConfig)

  val imageTrackerListener =
      object : ImageTracker() {
        private var isTracking = false

        override fun onImageBind(drawable: FrescoDrawableInterface) {
          ifEnabled {
            super.onImageBind(drawable)
            attachImageSelector(maybeGetContext(drawable))
          }
        }

        override fun onImageUnbind(drawable: FrescoDrawableInterface) {
          ifEnabled {
            super.onImageUnbind(drawable)
            if (drawableCount <= 0) {
              removeImageSelector()
            }
          }
        }

        private inline fun ifEnabled(body: () -> Unit) {
          if (isEnabled.get()) {
            body()
            isTracking = true
          } else if (isTracking) {
            removeImageSelector()
            super.reset()
            isTracking = false
          }
        }
      }

  var imageSelector: ImageSelector? = null

  private var isAttached: Boolean = false
  private var currentView: View? = null
  private var overlayPermissionStatus: Boolean? = null

  fun attachImageSelector(context: Context?) {
    if (isAttached || context == null || !canShowOverlays(context)) {
      // already attached or no context available to get the WindowManager from
      return
    }
    isAttached = true
    imageSelector =
        ImageSelector(
            imageTrackerListener,
            FrescoVitoProvider.getImagePipeline(),
            FrescoVitoProvider.getController())
    showImageToggleButtons(context)
  }

  fun removeImageSelector() {
    if (!isAttached) {
      return
    }
    val current = currentView ?: return

    if (!canShowOverlays(current.context)) {
      return
    }

    val windowManager = current.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    windowManager.removeView(current)
    currentView = null
    isAttached = false
  }

  private fun showImageToggleButtons(
      context: Context,
  ) {
    addWindow(context, createOnScreenButtons(context))
  }

  private fun showImageInfo(context: Context) {
    val windowContext = getWindowContext(context)
    addWindow(
        windowContext,
        LiveEditorUiUtils(imageSelector?.currentEditor, debugDataProviders)
            .createImageInfoView(windowContext) { showImageToggleButtons(windowContext) }
            .apply { background = ColorDrawable(editorBackgroundColor) })
  }

  private fun showLiveEditor(context: Context) {
    val windowContext = getWindowContext(context)
    addWindow(
        windowContext,
        LiveEditorUiUtils(imageSelector?.currentEditor, debugDataProviders)
            .createView(windowContext, customOptions.entries) {
              showImageToggleButtons(windowContext)
            }
            .apply { background = ColorDrawable(editorBackgroundColor) },
        DisplayMetrics()
            .apply { getWindowManager(context).defaultDisplay.getMetrics(this) }
            .heightPixels / 2)
  }

  private fun getWindowContext(context: Context): Context {
    val primaryDisplay =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          val displayManager: DisplayManager = context.getSystemService(DisplayManager::class.java)
          displayManager.getDisplay(DEFAULT_DISPLAY)
        } else {
          getWindowManager(context).defaultDisplay
        }

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      context
          .createDisplayContext(primaryDisplay)
          .createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null)
    } else {
      context.createDisplayContext(primaryDisplay)
    }
  }

  private fun getWindowManager(context: Context): WindowManager =
      context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

  private fun addWindow(
      context: Context,
      view: View,
      height: Int = WindowManager.LayoutParams.WRAP_CONTENT
  ) {
    if (!isEnabled.get()) {
      return
    }
    val padding = 16.dpToPx(context)
    val windowManager = getWindowManager(context)
    currentView?.apply { windowManager.removeView(this) }
    windowManager.addView(
        view,
        WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                height,
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
                else WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT)
            .apply {
              gravity = Gravity.BOTTOM or Gravity.RIGHT
              x = padding
              y = view.height
            })
    currentView = view
  }

  private fun createOnScreenButtons(context: Context): View {
    return LinearLayout(context).apply {
      orientation = LinearLayout.VERTICAL
      val cornerRadius = 16.dpToPxF(context)
      val buttonLayoutParams: LinearLayout.LayoutParams =
          LinearLayout.LayoutParams(
              LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
      buttonLayoutParams.bottomMargin = 8.dpToPx(context)
      val buttonPadding = 4.dpToPx(context)
      for (button in buttons) {
        addView(
            createButton(
                context,
                buttonPadding,
                buttonLayoutParams,
                cornerRadius,
                button.title,
                button.action))
      }
    }
  }

  private fun createButton(
      context: Context,
      paddingPx: Int,
      layoutParams: ViewGroup.LayoutParams,
      cornerRadius: Float,
      text: String,
      action: View.OnClickListener
  ): Button =
      Button(context).apply {
        setText(text)
        setTextColor(buttonTextColor)
        setPadding(paddingPx, paddingPx, paddingPx, paddingPx)
        setLayoutParams(layoutParams)
        setOnClickListener(action)
        background =
            GradientDrawable().apply {
              setColor(buttonBackgroundColor)
              shape = GradientDrawable.RECTANGLE
              setCornerRadius(cornerRadius)
            }
      }

  fun maybeGetContext(drawable: FrescoDrawableInterface): Context? =
      when (drawable) {
        is Drawable -> {
          when (val callback = drawable.callback) {
            is View -> callback.context
            else -> null
          }
        }
        else -> null
      }

  private fun canShowOverlays(context: Context): Boolean {
    overlayPermissionStatus?.let {
      return it
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
      showOverlayPermissionMessage(context)
      overlayPermissionStatus = false
    } else {
      overlayPermissionStatus = true
    }
    return overlayPermissionStatus ?: 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.tools.liveeditor

import android.content.Context
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.Spinner
import android.widget.TextView
import com.facebook.fresco.vito.core.impl.StringDebugDataProvider

class LiveEditorUiUtils(
    var liveEditor: ImageLiveEditor?,
    var debugDataProviders: List<StringDebugDataProvider>? = null
) {

  fun createView(
      context: Context,
      customEntries: List<ImageOptionsSampleValues.Entry<out Any>> = emptyList(),
      closeAction: ((View) -> Unit)? = null
  ): View =
      createScrollingList(context, closeAction) {
        addView(createWithList(context, ImageSourceSampleValues.entries))
        addView(createWithList(context, ImageOptionsSampleValues.roundingOptions))
        addView(createWithList(context, ImageOptionsSampleValues.borderOptions))
        addView(createWithList(context, ImageOptionsSampleValues.scaleTypes))
        addView(createWithList(context, ImageOptionsSampleValues.colorFilters))
        addView(createWithList(context, ImageOptionsSampleValues.fadingOptions))
        addView(createWithList(context, ImageOptionsSampleValues.autoPlay))
        addView(createWithList(context, ImageOptionsSampleValues.bitmapConfig))
        addView(createWithList(context, ImageOptionsSampleValues.resizeToViewportConfig))
        addView(
            createWithList(context, ImageOptionsSampleValues.localThumbnailPreviewsEnabledConfig))
        addView(createWithList(context, ImageOptionsSampleValues.progressiveRenderingEnabledConfig))
        addView(createWithList(context, ImageOptionsSampleValues.placeholderColors))

        customEntries.forEach { addView(createWithList(context, it)) }
      }

  private fun createScrollingList(
      context: Context,
      closeAction: ((View) -> Unit)? = null,
      block: LinearLayout.() -> Unit
  ): View =
      ScrollView(context).apply {
        layoutParams =
            LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        addView(
            LinearLayout(context).apply {
              layoutParams =
                  LinearLayout.LayoutParams(
                      ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
              orientation = LinearLayout.VERTICAL
              if (closeAction != null) {
                addView(createButton(context, "Close", closeAction))
              }
              block(this)
            })
      }

  fun createImageInfoView(context: Context, closeAction: (View) -> Unit): View =
      createScrollingList(context, closeAction) {
        var source: List<Pair<String, String>> =
            ImageSourceParser.convertSourceToKeyValue(liveEditor?.getSource().toString())
        liveEditor?.let { liveEditorNonNull ->
          debugDataProviders?.forEach {
            val debugData: Pair<String, String> =
                Pair(it.longName, it.extractData(liveEditorNonNull.drawable))
            source = source + debugData
          }
        }
        if (source.isEmpty()) {
          addView(TextView(context).apply { text = "Source is Empty" })
        }
        source.forEach {
          val view = ImageSourceUiUtil(context).createImageInfoView(it, this)
          addView(view)
        }
      }

  private fun createButton(context: Context, btnText: String, clickAction: (View) -> Unit): Button =
      Button(context).apply {
        text = btnText
        setPadding(paddingLeft, 8.dpToPx(context), paddingRight, 8.dpToPx(context))
        setOnClickListener(clickAction)
      }

  private fun <T> createWithList(
      context: Context,
      entry: ImageOptionsSampleValues.Entry<T>
  ): Spinner {
    return Spinner(context).apply {
      adapter =
          ArrayAdapter(
              context,
              android.R.layout.simple_spinner_dropdown_item,
              listOf(entry.name + ": original") + entry.data.map { entry.name + ": " + it.first })
      onItemSelectedListener =
          object : AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(p0: AdapterView<*>?) = Unit

            override fun onItemSelected(
                parent: AdapterView<*>?,
                view: View?,
                position: Int,
                id: Long
            ) {
              if (position > 0) {
                liveEditor?.editOptions(context) {
                  entry.updateFunction(it, entry.data[position - 1].second)
                }
              }
            }
          }
    }
  }

  private fun <T> createWithList(
      context: Context,
      entry: ImageSourceSampleValues.Entry<T>
  ): Spinner {
    return Spinner(context).apply {
      adapter =
          ArrayAdapter(
              context,
              android.R.layout.simple_spinner_dropdown_item,
              listOf(entry.name + ": original") + entry.data.map { entry.name + ": " + it.first })
      onItemSelectedListener =
          object : AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(p0: AdapterView<*>?) = Unit

            override fun onItemSelected(
                parent: AdapterView<*>?,
                view: View?,
                position: Int,
                id: Long
            ) {
              if (position == 0) {
                liveEditor?.editSource(context) { liveEditor?.getOriginalSource() ?: it }
              } else {
                liveEditor?.editSource(context) {
                  entry.updateFunction(it, entry.data[position - 1].second)
                }
              }
            }
          }
    }
  }

  companion object {
    internal fun Int.dpToPxF(context: Context): Float =
        TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), context.resources.displayMetrics)

    internal fun Int.dpToPx(context: Context): Int = dpToPxF(context).toInt()
  }
}


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

import android.net.Uri
import android.view.View
import com.facebook.fresco.vito.core.VitoImageRequest
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.source.ImageSource
import com.facebook.fresco.vito.source.ImageSourceProvider
import com.facebook.fresco.vito.view.impl.VitoViewImpl2

/** Load images into an ImageView or a plain View. */
object VitoView {

  /*
   * Display an image with default image options
   */
  @JvmStatic
  fun show(uri: Uri?, target: View) {
    show(ImageSourceProvider.forUri(uri), target)
  }

  /*
   * Display an image with default image options and a caller context.
   */
  @JvmStatic
  fun show(uri: Uri?, callerContext: Any, target: View) {
    show(ImageSourceProvider.forUri(uri), callerContext, target)
  }

  /*
   * Display an image with default image options
   */
  @JvmStatic
  fun show(imageSource: ImageSource, target: View) {
    VitoViewImpl2.show(imageSource, ImageOptions.defaults(), null, null, target)
  }

  /*
   * Display an image with default image options and a caller context.
   */
  @JvmStatic
  fun show(imageSource: ImageSource, callerContext: Any, target: View) {
    VitoViewImpl2.show(imageSource, ImageOptions.defaults(), callerContext, null, target)
  }

  /*
   * Display an image with the given image options
   */
  @JvmStatic
  fun show(uri: Uri?, imageOptions: ImageOptions, target: View) {
    show(ImageSourceProvider.forUri(uri), imageOptions, target)
  }

  /*
   * Display an image with the given image options
   */
  @JvmStatic
  fun show(imageSource: ImageSource, imageOptions: ImageOptions, target: View) {
    VitoViewImpl2.show(imageSource, imageOptions, null, null, target)
  }

  /*
   * Display an image
   */
  @JvmStatic
  fun show(uri: Uri?, imageOptions: ImageOptions, callerContext: Any?, target: View) {
    show(ImageSourceProvider.forUri(uri), imageOptions, callerContext, target)
  }

  /*
   * Display an image
   */
  @JvmStatic
  fun show(
      imageSource: ImageSource,
      imageOptions: ImageOptions,
      callerContext: Any?,
      target: View
  ) {
    VitoViewImpl2.show(imageSource, imageOptions, callerContext, null, target)
  }

  /*
   * Display an image
   */
  @JvmStatic
  fun show(
      uri: Uri?,
      imageOptions: ImageOptions,
      callerContext: Any?,
      imageListener: ImageListener?,
      target: View
  ) {
    show(ImageSourceProvider.forUri(uri), imageOptions, callerContext, imageListener, target)
  }

  /*
   * Display an image
   */
  @JvmStatic
  fun show(
      imageSource: ImageSource,
      imageOptions: ImageOptions,
      callerContext: Any?,
      imageListener: ImageListener?,
      target: View
  ) {
    VitoViewImpl2.show(imageSource, imageOptions, callerContext, imageListener, target)
  }

  @JvmStatic
  fun show(
      vitoImageRequest: VitoImageRequest,
      callerContext: Any?,
      imageListener: ImageListener?,
      target: View
  ) {
    VitoViewImpl2.show(vitoImageRequest, callerContext, imageListener, target)
  }

  /**
   * Release the image for the given view target if loaded by Vito
   *
   * @param target the image target to release
   */
  @JvmStatic
  fun release(target: View) {
    VitoViewImpl2.release(target)
  }
}


/*
 * 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.view.impl

import android.graphics.drawable.Drawable
import android.os.Build
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.facebook.common.internal.Supplier
import com.facebook.common.internal.Suppliers
import com.facebook.drawee.drawable.VisibilityCallback
import com.facebook.fresco.vito.core.FrescoDrawableInterface
import com.facebook.fresco.vito.core.VitoImageRequest
import com.facebook.fresco.vito.listener.ImageListener
import com.facebook.fresco.vito.options.ImageOptions
import com.facebook.fresco.vito.provider.FrescoVitoProvider
import com.facebook.fresco.vito.source.ImageSource

/** Vito View implementation */
object VitoViewImpl2 {
  @JvmField var useVisibilityCallbacks: Supplier<Boolean> = Suppliers.BOOLEAN_TRUE
  @JvmField var useSimpleFetchLogic: Supplier<Boolean> = Suppliers.BOOLEAN_FALSE

  private val onAttachStateChangeListenerCallback: OnAttachStateChangeListener =
      object : OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(view: View) {
          getDrawable(view)?.apply {
            imagePerfListener.onImageMount(this)
            maybeFetchImage(this)
          }
        }

        override fun onViewDetachedFromWindow(view: View) {
          getDrawable(view)?.apply {
            imagePerfListener.onImageUnmount(this)
            FrescoVitoProvider.getController().release(this)
          }
        }
      }

  @JvmStatic
  fun show(
      imageSource: ImageSource,
      imageOptions: ImageOptions,
      callerContext: Any?,
      imageListener: ImageListener?,
      target: View
  ) {
    show(
        FrescoVitoProvider.getImagePipeline()
            .createImageRequest(target.resources, imageSource, imageOptions),
        callerContext,
        imageListener,
        target)
  }

  @JvmStatic
  fun show(
      imageRequest: VitoImageRequest,
      callerContext: Any?,
      imageListener: ImageListener?,
      target: View
  ) {
    val frescoDrawable = ensureDrawableSet(target)
    // The Drawable might be re-purposed before being cleaned up, so we release if necessary.
    val oldImageRequest = frescoDrawable.imageRequest
    if (oldImageRequest != null && oldImageRequest != imageRequest) {
      FrescoVitoProvider.getController().releaseImmediately(frescoDrawable)
    }
    frescoDrawable.refetchRunnable = Runnable {
      FrescoVitoProvider.getController()
          .fetch(
              frescoDrawable = frescoDrawable,
              imageRequest = imageRequest,
              callerContext = callerContext,
              contextChain = null,
              listener = imageListener,
              perfDataListener = null,
              onFadeListener = null,
              viewportDimensions = null)
    }
    if (useSimpleFetchLogic.get()) {
      frescoDrawable.imagePerfListener.onImageMount(frescoDrawable)
      maybeFetchImage(frescoDrawable)
    } else {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // If the view is already attached to the window, immediately fetch the image.
        // Otherwise, the fetch will be submitted later when then View is attached.
        if (target.isAttachedToWindow) {
          frescoDrawable.imagePerfListener.onImageMount(frescoDrawable)
          maybeFetchImage(frescoDrawable)
        }
      } else {
        // Before Kitkat we don't have a good way to know.
        // Normally we expect the view to be already attached, thus we always fetch the image.
        frescoDrawable.imagePerfListener.onImageMount(frescoDrawable)
        maybeFetchImage(frescoDrawable)
      }
    }

    // `addOnAttachStateChangeListener` is not idempotent
    target.removeOnAttachStateChangeListener(onAttachStateChangeListenerCallback)
    target.addOnAttachStateChangeListener(onAttachStateChangeListenerCallback)
  }

  @JvmStatic
  fun release(target: View) {
    getDrawable(target)?.apply {
      imagePerfListener.onImageUnmount(this)
      FrescoVitoProvider.getController().releaseImmediately(this)
      refetchRunnable = null
    }
  }

  private fun maybeFetchImage(drawable: FrescoDrawableInterface) {
    drawable.refetchRunnable?.run()
  }

  /**
   * Ensure that a [FrescoDrawableInterface] is set for the given View target
   *
   * @param target the target to use
   * @return The drawable to use for the given target
   */
  private fun ensureDrawableSet(target: View): FrescoDrawableInterface {
    return when (target) {
      is ImageView ->
          when (val current = target.drawable) {
            is FrescoDrawableInterface -> current
            else ->
                createDrawable().also {
                  // Force the Drawable to adjust its bounds to match the hosting ImageView's
                  // bounds, since Fresco has custom scale types that are separate from ImageView's
                  // scale type.
                  // Without this, the Drawable would not respect the given Fresco ScaleType,
                  // effectively resulting in CENTER_INSIDE.
                  target.scaleType = ImageView.ScaleType.FIT_XY
                  target.setImageDrawable(it as Drawable)
                }
          }
      else ->
          when (val current = target.background) {
            is FrescoDrawableInterface -> current
            else -> createDrawable().also { ViewCompat.setBackground(target, it as Drawable) }
          }
    }
  }

  private fun getDrawable(view: View): FrescoDrawableInterface? {
    return (if (view is ImageView) view.drawable else view.background) as? FrescoDrawableInterface
  }

  private fun createDrawable(): FrescoDrawableInterface {
    val frescoDrawable: FrescoDrawableInterface =
        FrescoVitoProvider.getController().createDrawable()
    if (useVisibilityCallbacks.get()) {
      frescoDrawable.setVisibilityCallback(
          object : VisibilityCallback {
            override fun onVisibilityChange(visible: Boolean) {
              if (!visible) {
                FrescoVitoProvider.getController().release(frescoDrawable)
              } else {
                maybeFetchImage(frescoDrawable)
              }
            }

            override fun onDraw() {
              // NOP
            }
          })
    }
    return frescoDrawable
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

internal class Annotations {

  @SensitiveSourceMarker fun markedSource() = ""

  @SensitiveSinkMarker fun markedSink(input: String?) {}

  @SanitizerMarker fun markedSanitizer(input: String): String = input

  fun unmarkedSource() = ""

  fun unmarkedSink(input: String?) {}

  fun unmarkedSanitizer(input: String): String = input

  fun markedSourceToMarkedSinkBad() {
    markedSink(markedSource())
  }

  fun markedSourceUsingMarkedSanitizerToMarkedSinkOk() {
    markedSink(markedSanitizer(markedSource()))
  }

  fun markedSourceUsingUnmarkedSanitizerToMarkedSinkBad() {
    markedSink(unmarkedSanitizer(markedSource()))
  }

  fun unmarkedSourceToMarkedSinkOk() {
    markedSink(unmarkedSource())
  }

  fun unmarkedSourceUsingMarkedSanitizerToMarkedSinkOk() {
    markedSink(markedSanitizer(unmarkedSource()))
  }

  fun markedSourceToUnmarkedSinkOk() {
    unmarkedSink(markedSource())
  }

  fun unmarkedSourceToUnmarkedSinkOk() {
    unmarkedSink(unmarkedSource())
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

class Arrays {

  class Obj {
    var f: Any? = null
    var arr: Array<Any?>? = null
  }

  /** should report on these tests */
  fun viaArrayBad() {
    val arr = arrayOfNulls<Any>(1)
    arr[0] = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(arr[0])
  }

  fun viaArrayThenFieldBad() {
    val arr = arrayOfNulls<Obj>(1)
    arr[0]!!.f = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(arr[0]!!.f)
  }

  fun viaFieldThenArrayBad1(obj: Obj) {
    obj.arr!![0] = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(obj.arr!![0])
  }

  fun viaFieldThenArrayBad2() {
    val obj = Obj()
    obj.arr = arrayOfNulls(1)
    obj.arr!![0] = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(obj.arr!![0])
  }

  /** should not report on these tests */
  fun viaArrayOk() {
    val arr = arrayOfNulls<Any>(1)
    arr[0] = Any()
    InferTaint.inferSensitiveSink(arr[0])
  }

  fun viaArrayOk1(y: Any?, z: Array<Any?>?) {
    val arr = arrayOfNulls<Any>(2)
    arr[0] = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(arr[1])
  }

  fun viaArrayOk2(y: Any?, z: Array<Any?>?) {
    val arr = arrayOfNulls<Any>(1)
    arr[0] = InferTaint.inferSecretSource()
    arr[0] = null
    InferTaint.inferSensitiveSink(arr[0])
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

/** testing basic intraprocedural functionality: assignment, ifs, loops, casts */
class Basics {

  external fun notASource(): Any

  external fun notASink(o: Any)

  /** should report on these tests */
  fun directBad() {
    InferTaint.inferSensitiveSink(InferTaint.inferSecretSource())
  }

  fun viaVarBad1() {
    val src = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(src)
  }

  fun viaVarBad2() {
    val src = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(src)
  }

  fun viaVarBad3() {
    var src: Any? = InferTaint.inferSecretSource()
    val alias = src as Any
    src = null
    InferTaint.inferSensitiveSink(alias)
  }

  fun viaCastBad1() {
    InferTaint.inferSensitiveSink((InferTaint.inferSecretSource() as String))
  }

  fun viaCastBad2() {
    val src = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink((src as String))
  }

  fun ifBad1(b: Boolean) {
    var src: Any? = null
    if (b) {
      src = InferTaint.inferSecretSource()
    }
    InferTaint.inferSensitiveSink(src)
  }

  fun ifBad2(b: Boolean) {
    var src: Any? = InferTaint.inferSecretSource()
    if (b) {
      src = null
    }
    InferTaint.inferSensitiveSink(src)
  }

  fun ifBad3(b: Boolean) {
    val src: Any =
        if (b) {
          Any()
        } else {
          InferTaint.inferSecretSource()
        }
    InferTaint.inferSensitiveSink(src)
  }

  fun ifBad4(b1: Boolean, b2: Boolean) {
    val src: Any? =
        if (b1) {
          Any()
        } else if (b2) {
          InferTaint.inferSecretSource()
        } else {
          null
        }
    InferTaint.inferSensitiveSink(src)
  }

  fun ifBad5(b: Boolean) {
    val src = InferTaint.inferSecretSource()
    if (b) {
      InferTaint.inferSensitiveSink(src)
    }
  }

  fun switchBad1(i: Int) {
    val src = InferTaint.inferSecretSource()
    when (i) {
      1 -> InferTaint.inferSensitiveSink(src)
      2 -> {}
      else -> {}
    }
  }

  fun switchBad2(i: Int) {
    val src = InferTaint.inferSecretSource()
    when (i) {
      1 -> {}
      2 -> InferTaint.inferSensitiveSink(src)
      else -> {}
    }
  }

  fun whileBad1(input: Int) {
    var i = input
    val src = InferTaint.inferSecretSource()
    while (i < 10) {
      InferTaint.inferSensitiveSink(src)
      i++
    }
  }

  fun whileBad2(input: Int) {
    var i = input
    var src: Any? = null
    while (i < 10) {
      src = InferTaint.inferSecretSource()
      i++
    }
    InferTaint.inferSensitiveSink(src)
  }

  // this should report only two alarms, not three
  fun noTripleReportBad() {
    val src = InferTaint.inferSecretSource()
    InferTaint.inferSensitiveSink(src)
    InferTaint.inferSensitiveSink(src)
  }

  fun arrayWithTaintedContentsBad() {
    val src = InferTaint.inferSecretSource()
    val arr = arrayOf(src)
    InferTaint.inferSensitiveSink(arr)
  }

  fun passToSinkOk(x: Int, src: Any?) {
    InferTaint.inferSensitiveSink(src)
  }

  fun funCallBad() {
    val src = InferTaint.inferSecretSource()
    passToSinkOk(2, src)
  }

  /** should not report on these tests */
  fun directOk1() {
    notASink(notASource())
  }

  fun directOk2() {
    notASink(InferTaint.inferSecretSource())
  }

  fun directOk3() {
    InferTaint.inferSensitiveSink(notASource())
  }

  fun viaVarOk() {
    val src = Any()
    InferTaint.inferSensitiveSink(src)
  }

  fun viaVarStrongUpdateOk() {
    var src: Any? = InferTaint.inferSecretSource()
    src = null
    InferTaint.inferSensitiveSink(src)
  }

  fun exceptionOk(b: Boolean, o: Any): Any {
    if (b) {
      throw AssertionError("exception")
    }
    o.toString()
    return o
  }

  fun synchronizedOk(o: Any) {
    synchronized(o) {}
  }

  // this is to test that we don't crash due to the slightly odd translation of synchronized
  fun callSynchronizedOk(o: Any) {
    synchronizedOk(o)
  }

  fun deadCodeOk() {
    val src = InferTaint.inferSecretSource()
    val b = false
    if (b) {
      InferTaint.inferSensitiveSink(src)
    }
  }

  fun loopInvariantOk() {
    var src: Any? = InferTaint.inferSecretSource()
    for (i in 0..9) {
      src = null
    }
    InferTaint.inferSensitiveSink(src)
  }

  fun taintedToSanitizedToSinkOk() {
    InferTaint.inferSensitiveSink(
        InferTaint.inferUniversalSanitizer(InferTaint.inferSecretSource()))
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

fun interface Callback {
  fun onCompletion(result: Any)
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

import codetoanalyze.kotlin.pulse.InferTaint.inferSensitiveSink

class Constants {

  fun source(request: Any?) {}

  fun nullNotTaintedOk() {
    source(null)
    InferTaint.inferSensitiveSink(null)
  }

  fun stringLiteralNotTaintedOk() {
    source("asdf")
    InferTaint.inferSensitiveSink("asdf")
  }

  fun nonLiteralTaintedBad() {
    val obj = Any()
    source(obj)
    inferSensitiveSink(obj)
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

/**
 * To test taint config based a class name and method return type. In that case only methods
 * returning `String` are considered as sources.
 */
open class InferBaseSource {

  fun inferBaseSecretSource() = "secret"

  fun inferBaseNotSource() = Any()
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

/**
 * To test taint config based a class name and method return type. In that case only methods
 * returning `String` are considered as sources.
 */
class InferChildSource : InferBaseSource() {

  fun inferChildSecretSource() = "secret"

  fun inferChildNotSource() = Any()
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse
/**
 * WARNING! These methods are for testing the taint analysis only! Don't use them in models or in
 * real code.
 */
object InferTaint {

  fun object_undefined(): Any {
    return Any()
  }

  // these are to test whether we can add a taint spec to methods that have an implementation
  fun inferSecretSource(): Any {
    return object_undefined()
  }

  fun inferSensitiveSink(iMightBeTainted: Any?) {}

  fun inferUniversalSanitizer(iMightBeTainted: Any): Any {
    return iMightBeTainted
  }

  // these are to test whether we can add a taint spec to undefined methods
  external fun inferSecretSourceUndefined(): Any

  external fun inferSensitiveSinkUndefined(iMightBeTainted: Any?)

  // these are to tests that only calls of functions with the same names from InferTaintSinks are
  // recognized as sinks based on class_name_regex config
  fun sink1(iMightBeTainted: Any?) {}

  fun sink2(iMightBeTainted: Any?) {}

  fun addCallback(callback: Callback) {}

  // fun addCallback(callback: Function<Any>) {}
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse.sinks

/**
 * WARNING! These methods are for testing the taint analysis only! Don't use them in models or in
 * real code.
 */
object InferTaintSinks {

  fun sink1(iMightBeTainted: Any?) {}

  fun sink2(iMightBeTainted: Any?) {}
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

annotation class SanitizerMarker


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

annotation class SensitiveSinkMarker


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

annotation class SensitiveSourceMarker


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

class TaintedThis {
  private val field = "instanceField"

  fun taintThisBad() {
    InferTaint.inferSensitiveSink(field)
  }

  fun thisIsNotTaintedOk() {
    InferTaint.inferSensitiveSink(field)
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

class TaintFormals {
  // first parameter is tainted based on the config
  fun firstParameterTainted(tainted: Any, notTainted: Any) {
    // should be tainted
    InferTaint.inferSensitiveSink(tainted)
    // should not be tainted
    InferTaint.inferSensitiveSink(notTainted)
  }

  fun callbackAnonymousClassTaintedBad() {
    InferTaint.addCallback(
        object : Callback {
          // result parameter is tainted based on the config
          override fun onCompletion(result: Any) {
            InferTaint.inferSensitiveSink(result)
          }
        })
  }

  fun callbackLambdaTaintedBad() {
    InferTaint.addCallback { result ->
      // result parameter is tainted based on the config
      InferTaint.inferSensitiveSink(result)
    }
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
package codetoanalyze.kotlin.pulse

import codetoanalyze.kotlin.pulse.sinks.InferTaintSinks

/* testing advanced source/sink matchers */
class TaintMatchers {
  fun taintedBasedOnClassNameRegexBad() {
    val src = InferTaint.inferSecretSource()
    InferTaintSinks.sink1(src)
    InferTaintSinks.sink2(src)
  }

  fun notTaintedBasedOnClassNameRegexOk() {
    val src = InferTaint.inferSecretSource()
    InferTaint.sink1(src)
    InferTaint.sink2(src)
  }

  fun taintedFromInferBaseSourceBad() {
    val ics = InferChildSource()
    val source = ics.inferBaseSecretSource()
    InferTaint.inferSensitiveSink(source)
  }

  fun notTaintedFromInferBaseNotSourceGood() {
    val ics = InferChildSource()
    val notSource = ics.inferBaseNotSource()
    InferTaint.inferSensitiveSink(notSource)
  }

  fun taintedFromInferChildSourceBad() {
    val ics = InferChildSource()
    val source = ics.inferChildSecretSource()
    InferTaint.inferSensitiveSink(source)
  }

  fun notTaintedFromInferChildNotSourceGood() {
    val ics = InferChildSource()
    val notSource = ics.inferChildNotSource()
    InferTaint.inferSensitiveSink(notSource)
  }
}


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

class Coro(public val o: Object) {
  val fibonacciSeq = sequence {
    var a = 0
    var b = 1

    yield(1)

    while (true) {
      yield(a + b)

      val tmp = a + b

      synchronized(this) { a = b }
      b = tmp
    }
  }
}


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

import com.google.common.annotations.VisibleForTesting
import javax.annotation.concurrent.GuardedBy

class GuardedByTests {
  @GuardedBy("this") private var unguardedBadField = 0

  fun unguardedBad() {
    unguardedBadField = 0
  }

  @GuardedBy("this") private var synchronizedField = 0

  @Synchronized
  fun synchronizedMethodOk() {
    synchronizedField = 0
  }

  fun synchronizedBlockOk() {
    synchronized(this) { synchronizedField = 0 }
  }

  @GuardedBy("this") private var unguardedReadField = 0

  fun unguardedReadOk(): Int {
    return unguardedReadField
  }

  @GuardedBy("this") private var privateUnguardedField = 0

  private fun privateUnguardedOk() {
    privateUnguardedField = 0
  }

  fun interprocUnguardedBad() {
    privateUnguardedOk()
  }

  @GuardedBy("this") private var privateInterprocSynchronizedField = 0

  private fun privateInterprocSynchronizedOk() {
    privateInterprocSynchronizedField = 0
  }

  @Synchronized
  fun interprocSynchronizedOk() {
    privateInterprocSynchronizedOk()
  }

  @GuardedBy("this") private var visibleForTestingField = 0

  @VisibleForTesting
  fun visibleForTestingOk() {
    visibleForTestingField = 0
  }
}


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

class Hil(public val o: Object) {
  fun crashInfer(nums: List<Long>) {
    val foldedNums = nums.fold(1) { _, _ -> 2 }

    val checked = checkNotNull(o) { "Boom" }

    val v = foldedNums.toLong()
  }
}


/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream

fun readFileLineByLineUsingForEachLineOk(fileName: String) =
    File(fileName).forEachLine { println(it) }

fun FP_InputOutputStreamOk(fileName: String) {
  val fos = FileOutputStream(fileName)
  val fis = FileInputStream(fileName)
  fis.use { input -> fos.use { output -> output.write(input.read()) } }
}

fun inputOutputStreamBad(fileName: String) {
  val fos = FileOutputStream(fileName)
  val fis = FileInputStream(fileName)
  fis.use { input -> fos.write(input.read()) }
}


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

import kotlin.reflect.KClass
import kotlin.reflect.KProperty

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ScopeType(val value: KClass<*>)

class Inner {}

class Outer {}

@ScopeType(value = Inner::class) class InnerScopedClass {}

class InnerScope {}

@ScopeType(value = Outer::class)
public class OuterHoldsInner {
  private val f_bad: InnerScopedClass
  private val g_bad: InnerScopedClass = InnerScopedClass()
  private val delegate_FN: InnerScopedClass by InnerScopedClassDelegate()

  init {
    this.f_bad = InnerScopedClass()
  }
}

class InnerScopedClassDelegate {
  operator fun getValue(thisRef: Any?, property: KProperty<*>): InnerScopedClass {
    return InnerScopedClass()
  }
}

@ScopeType(value = Outer::class)
class AnotherOuterHoldsInner {
  private val f_bad: InnerScopedClass = InnerScopedClass()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.ktfmt.cli

import com.facebook.ktfmt.format.Formatter
import com.facebook.ktfmt.format.ParseError
import com.google.googlejavaformat.FormattingError
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.PrintStream
import java.util.concurrent.atomic.AtomicInteger
import kotlin.system.exitProcess

class Main(
    private val input: InputStream,
    private val out: PrintStream,
    private val err: PrintStream,
    args: Array<String>
) {
  companion object {
    @JvmStatic
    fun main(args: Array<String>) {
      exitProcess(Main(System.`in`, System.out, System.err, args).run())
    }

    /**
     * expandArgsToFileNames expands 'args' to a list of .kt files to format.
     *
     * Most commonly, 'args' is either a list of .kt files, or a name of a directory whose contents
     * the user wants to format.
     */
    fun expandArgsToFileNames(args: List<String>): List<File> {
      if (args.size == 1 && File(args[0]).isFile) {
        return listOf(File(args[0]))
      }
      val result = mutableListOf<File>()
      for (arg in args) {
        if (arg == "-") {
          error(
              "Error: '-', which causes ktfmt to read from stdin, should not be mixed with file name")
        }
        result.addAll(
            File(arg).walkTopDown().filter {
              it.isFile && (it.extension == "kt" || it.extension == "kts")
            })
      }
      return result
    }
  }

  private val parsedArgs: ParsedArgs = ParsedArgs.processArgs(err, args)

  fun run(): Int {
    if (parsedArgs.fileNames.isEmpty()) {
      err.println(
          "Usage: ktfmt [--dropbox-style | --google-style | --kotlinlang-style] [--dry-run] [--set-exit-if-changed] [--stdin-name=<name>] [--do-not-remove-unused-imports] File1.kt File2.kt ...")
      err.println("Or: ktfmt @file")
      return 1
    }

    if (parsedArgs.fileNames.size == 1 && parsedArgs.fileNames[0] == "-") {
      return try {
        val alreadyFormatted = format(null)
        if (!alreadyFormatted && parsedArgs.setExitIfChanged) 1 else 0
      } catch (e: Exception) {
        1
      }
    } else if (parsedArgs.stdinName != null) {
      err.println("Error: --stdin-name can only be used with stdin")
      return 1
    }

    val files: List<File>
    try {
      files = expandArgsToFileNames(parsedArgs.fileNames)
    } catch (e: java.lang.IllegalStateException) {
      err.println(e.message)
      return 1
    }

    if (files.isEmpty()) {
      err.println("Error: no .kt files found")
      return 1
    }

    val retval = AtomicInteger(0)
    files.parallelStream().forEach {
      try {
        if (!format(it) && parsedArgs.setExitIfChanged) {
          retval.set(1)
        }
      } catch (e: Exception) {
        retval.set(1)
      }
    }
    return retval.get()
  }

  /**
   * Handles the logic for formatting and flags.
   *
   * If dry run mode is active, this simply prints the name of the [source] (file path or `<stdin>`)
   * to [out]. Otherwise, this will run the appropriate formatting as normal.
   *
   * @param file The file to format. If null, the code is read from <stdin>.
   * @return true iff input is valid and already formatted.
   */
  private fun format(file: File?): Boolean {
    val fileName = file?.toString() ?: parsedArgs.stdinName ?: "<stdin>"
    try {
      val code = file?.readText() ?: BufferedReader(InputStreamReader(input)).readText()
      val formattedCode = Formatter.format(parsedArgs.formattingOptions, code)
      val alreadyFormatted = code == formattedCode

      // stdin
      if (file == null) {
        if (parsedArgs.dryRun) {
          if (!alreadyFormatted) {
            out.println(fileName)
          }
        } else {
          out.print(formattedCode)
        }
        return alreadyFormatted
      }

      if (parsedArgs.dryRun) {
        if (!alreadyFormatted) {
          out.println(fileName)
        }
      } else {
        // TODO(T111284144): Add tests
        if (!alreadyFormatted) {
          file.writeText(formattedCode)
        }
        err.println("Done formatting $fileName")
      }

      return alreadyFormatted
    } catch (e: IOException) {
      err.println("Error formatting $fileName: ${e.message}; skipping.")
      throw e
    } catch (e: ParseError) {
      handleParseError(fileName, e)
      throw e
    } catch (e: FormattingError) {
      for (diagnostic in e.diagnostics()) {
        System.err.println("$fileName:$diagnostic")
      }
      e.printStackTrace(err)
      throw e
    }
  }

  private fun handleParseError(fileName: String, e: ParseError) {
    err.println("$fileName:${e.message}")
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.ktfmt.cli

import com.facebook.ktfmt.format.Formatter
import com.facebook.ktfmt.format.FormattingOptions
import java.io.File
import java.io.PrintStream

/** ParsedArgs holds the arguments passed to ktfmt on the command-line, after parsing. */
data class ParsedArgs(
    val fileNames: List<String>,
    val formattingOptions: FormattingOptions,
    /**
     * Run the formatter without writing changes to any files. This will print the path of any files
     * that would be changed if the formatter is run normally.
     */
    val dryRun: Boolean,

    /** Return exit code 1 if any formatting changes are detected. */
    val setExitIfChanged: Boolean,
    /** File name to report when formating code from stdin */
    val stdinName: String?,
) {
  companion object {

    fun processArgs(err: PrintStream, args: Array<String>): ParsedArgs {
      if (args.size == 1 && args[0].startsWith("@")) {
        return parseOptions(err, File(args[0].substring(1)).readLines().toTypedArray())
      } else {
        return parseOptions(err, args)
      }
    }

    /** parseOptions parses command-line arguments passed to ktfmt. */
    fun parseOptions(err: PrintStream, args: Array<String>): ParsedArgs {
      val fileNames = mutableListOf<String>()
      var formattingOptions = FormattingOptions()
      var dryRun = false
      var setExitIfChanged = false
      var removeUnusedImports = true
      var stdinName: String? = null

      for (arg in args) {
        when {
          arg == "--dropbox-style" -> formattingOptions = Formatter.DROPBOX_FORMAT
          arg == "--google-style" -> formattingOptions = Formatter.GOOGLE_FORMAT
          arg == "--kotlinlang-style" -> formattingOptions = Formatter.KOTLINLANG_FORMAT
          arg == "--dry-run" || arg == "-n" -> dryRun = true
          arg == "--set-exit-if-changed" -> setExitIfChanged = true
          arg == "--do-not-remove-unused-imports" -> removeUnusedImports = false
          arg.startsWith("--stdin-name") -> stdinName = parseKeyValueArg(err, "--stdin-name", arg)
          arg.startsWith("--") -> err.println("Unexpected option: $arg")
          arg.startsWith("@") -> err.println("Unexpected option: $arg")
          else -> fileNames.add(arg)
        }
      }

      return ParsedArgs(
          fileNames,
          formattingOptions.copy(removeUnusedImports = removeUnusedImports),
          dryRun,
          setExitIfChanged,
          stdinName,
      )
    }

    private fun parseKeyValueArg(err: PrintStream, key: String, arg: String): String? {
      val parts = arg.split('=', limit = 2)
      if (parts[0] != key || parts.size != 2) {
        err.println("Found option '${arg}', expected '${key}=<value>'")
        return null
      }
      return parts[1]
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.ktfmt.debughelpers

import com.google.common.collect.ImmutableList
import com.google.common.collect.Range
import com.google.googlejavaformat.CloseOp
import com.google.googlejavaformat.CommentsHelper
import com.google.googlejavaformat.Doc
import com.google.googlejavaformat.Input
import com.google.googlejavaformat.Op
import com.google.googlejavaformat.OpenOp
import com.google.googlejavaformat.OpsBuilder
import com.google.googlejavaformat.Output
import java.util.regex.Pattern

/** A regex to extract the indent size from OpenOp.toString(). */
private val OPENOP_STRING_FORM_REGEX = Pattern.compile("""OpenOp\{plusIndent=Const\{n=(\d+)}}""")

/**
 * printOps prints a list of Ops in a hierarchical way.
 *
 * It's useful when debugging incorrect newlines.
 */
fun printOps(ops: ImmutableList<Op>) {
  println("Ops: ")
  var indent = 0
  for (op in ops) {
    val line =
        when (op) {
          is OpenOp -> {
            val matcher = OPENOP_STRING_FORM_REGEX.matcher(op.toString())
            if (matcher.matches()) {
              val opIndent = matcher.group(1)
              "[ " + if (opIndent != "0") opIndent else ""
            } else {
              "[ $op"
            }
          }
          is CloseOp -> "]"
          is Doc.Token -> {
            var result: String? = ""
            val output =
                object : Output() {
                  override fun indent(indent: Int) = Unit

                  override fun blankLine(k: Int, wanted: OpsBuilder.BlankLineWanted?) = Unit

                  override fun markForPartialFormat(start: Input.Token?, end: Input.Token?) = Unit

                  override fun getCommentsHelper(): CommentsHelper {
                    throw Throwable()
                  }

                  override fun append(text: String?, range: Range<Int>?) {
                    result = text
                  }
                }
            op.write(output)
            """"$result""""
          }
          else -> {
            val result = op.toString()
            if (result == "Space{}") "\" \"" else result
          }
        }
    if (op is CloseOp) {
      indent--
    }
    repeat(2 * indent) { print(" ") }
    if (op is OpenOp) {
      indent++
    }
    println(line)
  }
  println()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.ktfmt.debughelpers

import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid

class PrintAstVisitor : KtTreeVisitorVoid() {
  private var depth = 0

  override fun visitElement(element: PsiElement) {
    print("  ".repeat(depth))
    println("${element.javaClass.simpleName} ${element.text?.replace("\n", "\\n")}")
    depth++
    super.visitElement(element)
    depth--
  }
}