@file:JvmName("ViewUtils")

package org.schabi.newpipe.ktx

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.res.ColorStateList
import android.util.Log
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.core.animation.addListener
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import org.schabi.newpipe.MainActivity

private const val TAG = "ViewUtils"

inline var View.backgroundTintListCompat: ColorStateList?
    get() = ViewCompat.getBackgroundTintList(this)
    set(value) = ViewCompat.setBackgroundTintList(this, value)

/**
 * Animate the view.
 *
 * @param enterOrExit   true to enter, false to exit
 * @param duration      how long the animation will take, in milliseconds
 * @param animationType Type of the animation
 * @param delay         how long the animation will wait to start, in milliseconds
 * @param execOnEnd     runnable that will be executed when the animation ends
 */
@JvmOverloads
fun View.animate(
    enterOrExit: Boolean,
    duration: Long,
    animationType: AnimationType = AnimationType.ALPHA,
    delay: Long = 0,
    execOnEnd: Runnable? = null
) {
    if (MainActivity.DEBUG) {
        val id = try {
            resources.getResourceEntryName(id)
        } catch (e: Exception) {
            id.toString()
        }
        val msg = String.format(
            "%8s →  [%s:%s] [%s %s:%s] execOnEnd=%s", enterOrExit,
            javaClass.simpleName, id, animationType, duration, delay, execOnEnd
        )
        Log.d(TAG, "animate(): $msg")
    }
    if (isVisible && enterOrExit) {
        if (MainActivity.DEBUG) {
            Log.d(TAG, "animate(): view was already visible > view = [$this]")
        }
        animate().setListener(null).cancel()
        isVisible = true
        alpha = 1f
        execOnEnd?.run()
        return
    } else if ((isGone || isInvisible) && !enterOrExit) {
        if (MainActivity.DEBUG) {
            Log.d(TAG, "animate(): view was already gone > view = [$this]")
        }
        animate().setListener(null).cancel()
        isGone = true
        alpha = 0f
        execOnEnd?.run()
        return
    }
    animate().setListener(null).cancel()
    isVisible = true

    when (animationType) {
        AnimationType.ALPHA -> animateAlpha(enterOrExit, duration, delay, execOnEnd)
        AnimationType.SCALE_AND_ALPHA -> animateScaleAndAlpha(enterOrExit, duration, delay, execOnEnd)
        AnimationType.LIGHT_SCALE_AND_ALPHA -> animateLightScaleAndAlpha(enterOrExit, duration, delay, execOnEnd)
        AnimationType.SLIDE_AND_ALPHA -> animateSlideAndAlpha(enterOrExit, duration, delay, execOnEnd)
        AnimationType.LIGHT_SLIDE_AND_ALPHA -> animateLightSlideAndAlpha(enterOrExit, duration, delay, execOnEnd)
    }
}

/**
 * Animate the background color of a view.
 *
 * @param duration   the duration of the animation
 * @param colorStart the background color to start with
 * @param colorEnd   the background color to end with
 */
fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) {
    if (MainActivity.DEBUG) {
        Log.d(
            TAG,
            "animateBackgroundColor() called with: " +
                "view = [" + this + "], duration = [" + duration + "], " +
                "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]"
        )
    }
    val empty = arrayOf(IntArray(0))
    val viewPropertyAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorStart, colorEnd)
    viewPropertyAnimator.interpolator = FastOutSlowInInterpolator()
    viewPropertyAnimator.duration = duration
    viewPropertyAnimator.addUpdateListener { animation: ValueAnimator ->
        backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int))
    }
    viewPropertyAnimator.addListener(
        onCancel = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) },
        onEnd = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) }
    )
    viewPropertyAnimator.start()
}

fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
    if (MainActivity.DEBUG) {
        Log.d(
            TAG,
            "animateHeight: duration = [" + duration + "], " +
                "from " + height + " to → " + targetHeight + " in: " + this
        )
    }
    val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat())
    animator.interpolator = FastOutSlowInInterpolator()
    animator.duration = duration
    animator.addUpdateListener { animation: ValueAnimator ->
        val value = animation.animatedValue as Float
        layoutParams.height = value.toInt()
        requestLayout()
    }
    animator.addListener(
        onCancel = {
            layoutParams.height = targetHeight
            requestLayout()
        },
        onEnd = {
            layoutParams.height = targetHeight
            requestLayout()
        }
    )
    animator.start()
    return animator
}

fun View.animateRotation(duration: Long, targetRotation: Int) {
    if (MainActivity.DEBUG) {
        Log.d(
            TAG,
            "animateRotation: duration = [" + duration + "], " +
                "from " + rotation + " to → " + targetRotation + " in: " + this
        )
    }
    animate().setListener(null).cancel()
    animate()
        .rotation(targetRotation.toFloat()).setDuration(duration)
        .setInterpolator(FastOutSlowInInterpolator())
        .setListener(object : AnimatorListenerAdapter() {
            override fun onAnimationCancel(animation: Animator) {
                rotation = targetRotation.toFloat()
            }

            override fun onAnimationEnd(animation: Animator) {
                rotation = targetRotation.toFloat()
            }
        }).start()
}

private fun View.animateAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
    if (enterOrExit) {
        animate().setInterpolator(FastOutSlowInInterpolator()).alpha(1f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    execOnEnd?.run()
                }
            }).start()
    } else {
        animate().setInterpolator(FastOutSlowInInterpolator()).alpha(0f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    isGone = true
                    execOnEnd?.run()
                }
            }).start()
    }
}

private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
    if (enterOrExit) {
        scaleX = .8f
        scaleY = .8f
        animate()
            .setInterpolator(FastOutSlowInInterpolator())
            .alpha(1f).scaleX(1f).scaleY(1f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    execOnEnd?.run()
                }
            }).start()
    } else {
        scaleX = 1f
        scaleY = 1f
        animate()
            .setInterpolator(FastOutSlowInInterpolator())
            .alpha(0f).scaleX(.8f).scaleY(.8f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    isGone = true
                    execOnEnd?.run()
                }
            }).start()
    }
}

private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
    if (enterOrExit) {
        alpha = .5f
        scaleX = .95f
        scaleY = .95f
        animate()
            .setInterpolator(FastOutSlowInInterpolator())
            .alpha(1f).scaleX(1f).scaleY(1f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    execOnEnd?.run()
                }
            }).start()
    } else {
        alpha = 1f
        scaleX = 1f
        scaleY = 1f
        animate()
            .setInterpolator(FastOutSlowInInterpolator())
            .alpha(0f).scaleX(.95f).scaleY(.95f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    isGone = true
                    execOnEnd?.run()
                }
            }).start()
    }
}

private fun View.animateSlideAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
    if (enterOrExit) {
        translationY = -height.toFloat()
        alpha = 0f
        animate()
            .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    execOnEnd?.run()
                }
            }).start()
    } else {
        animate()
            .setInterpolator(FastOutSlowInInterpolator())
            .alpha(0f).translationY(-height.toFloat())
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    isGone = true
                    execOnEnd?.run()
                }
            }).start()
    }
}

private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long, delay: Long, execOnEnd: Runnable?) {
    if (enterOrExit) {
        translationY = -height / 2.0f
        alpha = 0f
        animate()
            .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    execOnEnd?.run()
                }
            }).start()
    } else {
        animate().setInterpolator(FastOutSlowInInterpolator())
            .alpha(0f).translationY(-height / 2.0f)
            .setDuration(duration).setStartDelay(delay)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    isGone = true
                    execOnEnd?.run()
                }
            }).start()
    }
}

fun View.slideUp(
    duration: Long,
    delay: Long,
    @FloatRange(from = 0.0, to = 1.0) translationPercent: Float
) {
    slideUp(duration, delay, translationPercent, null)
}

fun View.slideUp(
    duration: Long,
    delay: Long = 0L,
    @FloatRange(from = 0.0, to = 1.0) translationPercent: Float = 1.0F,
    execOnEnd: Runnable? = null
) {
    val newTranslationY = (resources.displayMetrics.heightPixels * translationPercent).toInt()
    animate().setListener(null).cancel()
    alpha = 0f
    translationY = newTranslationY.toFloat()
    isVisible = true
    animate()
        .alpha(1f)
        .translationY(0f)
        .setStartDelay(delay)
        .setDuration(duration)
        .setInterpolator(FastOutSlowInInterpolator())
        .setListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                execOnEnd?.run()
            }
        })
        .start()
}

/**
 * Instead of hiding normally using [animate], which would make
 * the recycler view unable to capture touches after being hidden, this just animates the alpha
 * value setting it to `0.0` after `200` milliseconds.
 */
fun View.animateHideRecyclerViewAllowingScrolling() {
    // not hiding normally because the view needs to still capture touches and allow scroll
    animate().alpha(0.0f).setDuration(200).start()
}

enum class AnimationType {
    ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
}
