/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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.android.systemui.controls

import android.annotation.StringRes
import android.content.Context
import android.graphics.CornerPathEffect
import android.graphics.drawable.ShapeDrawable
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.widget.TextView
import com.android.systemui.Prefs
import com.android.systemui.res.R
import com.android.systemui.recents.TriangleShape

/**
 * Manager for showing an onboarding tooltip on screen.
 *
 * The tooltip can be made to appear below or above a point. The number of times it will appear
 * is determined by an shared preference (defined in [Prefs]).
 *
 * @property context A context to use to inflate the views and retrieve shared preferences from
 * @property preferenceName name of the preference to use to track the number of times the tooltip
 *                          has been shown.
 * @property maxTimesShown the maximum number of times to show the tooltip
 * @property below whether the tooltip should appear below (with up pointing arrow) or above (down
 *                 pointing arrow) the specified point.
 * @see [TooltipManager.show]
 */
class TooltipManager(
    context: Context,
    private val preferenceName: String,
    private val maxTimesShown: Int = 2,
    private val below: Boolean = true
) {

    companion object {
        private const val SHOW_DELAY_MS: Long = 500
        private const val SHOW_DURATION_MS: Long = 300
        private const val HIDE_DURATION_MS: Long = 100
    }

    private var shown = Prefs.getInt(context, preferenceName, 0)

    val layout: ViewGroup =
        LayoutInflater.from(context).inflate(R.layout.controls_onboarding, null) as ViewGroup
    val preferenceStorer = { num: Int ->
        Prefs.putInt(context, preferenceName, num)
    }

    init {
        layout.alpha = 0f
    }

    private val textView = layout.requireViewById<TextView>(R.id.onboarding_text)
    private val dismissView = layout.requireViewById<View>(R.id.dismiss).apply {
        setOnClickListener {
            hide(true)
        }
    }

    private val arrowView = layout.requireViewById<View>(R.id.arrow).apply {
        val typedValue = TypedValue()
        context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)
        val toastColor = context.resources.getColor(typedValue.resourceId, context.theme)
        val arrowRadius = context.resources.getDimensionPixelSize(
            R.dimen.recents_onboarding_toast_arrow_corner_radius)
        val arrowLp = layoutParams
        val arrowDrawable = ShapeDrawable(TriangleShape.create(
            arrowLp.width.toFloat(), arrowLp.height.toFloat(), below))
        val arrowPaint = arrowDrawable.paint
        arrowPaint.color = toastColor
        // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
        arrowPaint.pathEffect = CornerPathEffect(arrowRadius.toFloat())
        setBackground(arrowDrawable)
    }

    init {
        if (!below) {
            layout.removeView(arrowView)
            layout.addView(arrowView)
            (arrowView.layoutParams as ViewGroup.MarginLayoutParams).apply {
                bottomMargin = topMargin
                topMargin = 0
            }
        }
    }

    /**
     * Show the tooltip
     *
     * @param stringRes the id of the string to show in the tooltip
     * @param x horizontal position (w.r.t. screen) for the arrow point
     * @param y vertical position (w.r.t. screen) for the arrow point
     */
    fun show(@StringRes stringRes: Int, x: Int, y: Int) {
        if (!shouldShow()) return
        textView.setText(stringRes)
        shown++
        preferenceStorer(shown)
        layout.post {
            val p = IntArray(2)
            layout.getLocationOnScreen(p)
            layout.translationX = (x - p[0] - layout.width / 2).toFloat()
            layout.translationY = (y - p[1]).toFloat() - if (!below) layout.height else 0
            if (layout.alpha == 0f) {
                layout.animate()
                    .alpha(1f)
                    .withLayer()
                    .setStartDelay(SHOW_DELAY_MS)
                    .setDuration(SHOW_DURATION_MS)
                    .setInterpolator(DecelerateInterpolator())
                    .start()
            }
        }
    }

    /**
     * Hide the tooltip
     *
     * @param animate whether to animate the fade out
     */
    fun hide(animate: Boolean = false) {
        if (layout.alpha == 0f) return
        layout.post {
            if (animate) {
                layout.animate()
                    .alpha(0f)
                    .withLayer()
                    .setStartDelay(0)
                    .setDuration(HIDE_DURATION_MS)
                    .setInterpolator(AccelerateInterpolator())
                    .start()
            } else {
                layout.animate().cancel()
                layout.alpha = 0f
            }
        }
    }

    private fun shouldShow() = shown < maxTimesShown
}