package com.ponzischeme89.memby.ui.player

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.View
import com.ponzischeme89.memby.R
import kotlin.math.ceil
import kotlin.math.min

/**
 * The ring on the skip-intro button: how long is left to press it, drawn as a draining arc
 * with the figure inside.
 *
 * Like [PrerollCountdownView] it runs no animator of its own. PlayerActivity advances it
 * from the playhead, which is what keeps it honest — pausing during the titles holds the
 * ring where it is, and seeking moves it to wherever the film now is, neither of which a
 * timer counting wall-clock seconds could do.
 *
 * It takes its colours from its own drawable state rather than from a setter. The button
 * around it is a state-list pill — green with white text, white with dark text once
 * focused — so the ring has to change with it or it disappears into the fill the moment
 * somebody's remote reaches it. `duplicateParentState` in the layout is what feeds that
 * state down; without it this draws focused colours never.
 */
class SkipIntroCountdownView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {

    private val density = resources.displayMetrics.density
    private val ringBounds = RectF()
    private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeCap = Paint.Cap.ROUND
        strokeWidth = 2.5f * density
    }
    private val progressPaint = Paint(trackPaint)
    private val figurePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        textAlign = Paint.Align.CENTER
        typeface = Typeface.create("sans-serif", Typeface.BOLD)
    }

    private var figure = ""
    private var progress = 1f

    /**
     * How much of the offer is left, and how long it ran for.
     *
     * Redraws only when the drawn result would actually differ. This is advanced several
     * times a second for two minutes at a stretch, and a two-minute ring moves by a
     * fraction of a degree per tick — invalidating on every one of them would be a couple
     * of hundred pointless draws per episode on a box that has a decoder to feed.
     */
    fun setRemaining(remainingMs: Long, totalMs: Long) {
        val remaining = remainingMs.coerceAtLeast(0L)
        val nextFigure = formatRemaining(remaining)
        val nextProgress = if (totalMs > 0L) {
            (remaining.toFloat() / totalMs.toFloat()).coerceIn(0f, 1f)
        } else {
            0f
        }
        // A degree is about the smallest movement worth a redraw; below that the arc lands
        // on the same pixels.
        val moved = kotlin.math.abs(nextProgress - progress) * 360f >= 1f
        if (nextFigure == figure && !moved) return
        figure = nextFigure
        progress = nextProgress
        contentDescription = context.getString(R.string.player_skip_intro_countdown, nextFigure)
        invalidate()
    }

    override fun drawableStateChanged() {
        super.drawableStateChanged()
        // The focused pill is white, so everything on it has to go dark — the same
        // inversion `next_up_button_text` makes for the label beside this.
        val focused = isFocused || drawableState.contains(android.R.attr.state_focused)
        val ink = if (focused) FOCUSED_INK else Color.WHITE
        // The unspent part of the ring has to stay visible without competing with the arc.
        // Dark ink on the focused white pill needs more of itself than white does on green,
        // where the fill is already doing half the separating.
        trackPaint.color = Color.argb(
            if (focused) 92 else 72,
            Color.red(ink),
            Color.green(ink),
            Color.blue(ink),
        )
        progressPaint.color = ink
        figurePaint.color = ink
        invalidate()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val strokeInset = trackPaint.strokeWidth / 2f
        val diameter = min(width, height).toFloat()
        val left = (width - diameter) / 2f + strokeInset
        val top = (height - diameter) / 2f + strokeInset
        ringBounds.set(
            left,
            top,
            left + diameter - trackPaint.strokeWidth,
            top + diameter - trackPaint.strokeWidth,
        )
        canvas.drawOval(ringBounds, trackPaint)
        if (progress > 0f) {
            // Anticlockwise from the top, so the ring empties the way a clock hand would
            // sweep back rather than filling up as the thing it measures runs out.
            canvas.drawArc(ringBounds, -90f, -360f * progress, false, progressPaint)
        }
        if (figure.isEmpty()) return
        figurePaint.textSize = figureTextSize(diameter, figure.length)
        val baseline = height / 2f - (figurePaint.ascent() + figurePaint.descent()) / 2f
        canvas.drawText(figure, width / 2f, baseline, figurePaint)
    }

    /**
     * The figure has to fit inside the ring, and "1:58" is twice the width of "12". Sizing
     * from the string's own length is what stops a two-minute opening printing over its own
     * arc — an intro is commonly long enough to be counted in minutes, so this is the
     * ordinary case rather than the edge one.
     */
    private fun figureTextSize(diameter: Float, characters: Int): Float =
        diameter * if (characters >= 4) 0.30f else 0.42f

    private companion object {
        val FOCUSED_INK = Color.rgb(11, 14, 17)
    }
}

/**
 * "1:58" over a minute, "58" under it. Never "0:58": a bare figure is read at a glance in a
 * ring this size, and the colon is only worth its width once there are minutes to separate.
 */
internal fun formatRemaining(remainingMs: Long): String {
    val seconds = ceil(remainingMs.coerceAtLeast(0L) / 1_000.0).toInt()
    if (seconds < 60) return seconds.toString()
    return "${seconds / 60}:${(seconds % 60).toString().padStart(2, '0')}"
}
