/*
 * UltrasonicAppWidgetProvider.kt
 * Copyright (C) 2009-2022 Ultrasonic developers
 *
 * Distributed under terms of the GNU GPLv3 license.
 */

package org.moire.ultrasonic.provider

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Environment
import android.view.KeyEvent
import android.widget.RemoteViews
import java.lang.Exception
import org.moire.ultrasonic.R
import org.moire.ultrasonic.activity.NavigationActivity
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.imageloader.BitmapUtils
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
import org.moire.ultrasonic.util.Constants
import timber.log.Timber

/**
 * Widget Provider for the Ultrasonic Widgets
 */
@Suppress("MagicNumber")
open class UltrasonicAppWidgetProvider : AppWidgetProvider() {
    @JvmField
    protected var layoutId = 0
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        defaultAppWidget(context, appWidgetIds)
    }

    /**
     * Initialize given widgets to default state, where we launch Ultrasonic on default click
     * and hide actions if service not running.
     */
    private fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
        val res = context.resources
        val views = RemoteViews(context.packageName, layoutId)
        views.setTextViewText(R.id.title, null)
        views.setTextViewText(R.id.album, null)
        views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text))
        linkButtons(context, views, false)
        pushUpdate(context, appWidgetIds, views)
    }

    private fun pushUpdate(context: Context, appWidgetIds: IntArray?, views: RemoteViews) {
        // Update specific list of appWidgetIds if given, otherwise default to all
        val manager = AppWidgetManager.getInstance(context)
        if (manager != null) {
            if (appWidgetIds != null) {
                manager.updateAppWidget(appWidgetIds, views)
            } else {
                manager.updateAppWidget(ComponentName(context, this.javaClass), views)
            }
        }
    }

    /**
     * Handle a change notification coming over from [MediaPlayerController]
     */
    fun notifyChange(context: Context, currentSong: Track?, playing: Boolean, setAlbum: Boolean) {
        if (hasInstances(context)) {
            performUpdate(context, currentSong, playing, setAlbum)
        }
    }

    /**
     * Check against [AppWidgetManager] if there are any instances of this widget.
     */
    private fun hasInstances(context: Context): Boolean {
        val manager = AppWidgetManager.getInstance(context)
        if (manager != null) {
            val appWidgetIds = manager.getAppWidgetIds(ComponentName(context, javaClass))
            return appWidgetIds.isNotEmpty()
        }
        return false
    }

    /**
     * Update all active widget instances by pushing changes
     */
    private fun performUpdate(
        context: Context,
        currentSong: Track?,
        playing: Boolean,
        setAlbum: Boolean
    ) {
        Timber.d("Updating Widget")
        val res = context.resources
        val views = RemoteViews(context.packageName, layoutId)
        val title = currentSong?.title
        val artist = currentSong?.artist
        val album = currentSong?.album
        var errorState: CharSequence? = null

        // Show error message?
        val status = Environment.getExternalStorageState()
        if (status == Environment.MEDIA_SHARED || status == Environment.MEDIA_UNMOUNTED) {
            errorState = res.getText(R.string.widget_sdcard_busy)
        } else if (status == Environment.MEDIA_REMOVED) {
            errorState = res.getText(R.string.widget_sdcard_missing)
        } else if (currentSong == null) {
            errorState = res.getText(R.string.widget_initial_text)
        }
        if (errorState != null) {
            // Show error state to user
            views.setTextViewText(R.id.title, null)
            views.setTextViewText(R.id.artist, errorState)
            if (setAlbum) {
                views.setTextViewText(R.id.album, null)
            }
            views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
        } else {
            // No error, so show normal titles
            views.setTextViewText(R.id.title, title)
            views.setTextViewText(R.id.artist, artist)
            if (setAlbum) {
                views.setTextViewText(R.id.album, album)
            }
        }

        // Set correct drawable for pause state
        if (playing) {
            views.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal)
        } else {
            views.setImageViewResource(R.id.control_play, R.drawable.media_start_normal)
        }

        // Set the cover art
        try {
            val bitmap =
                if (currentSong == null) null else BitmapUtils.getAlbumArtBitmapFromDisk(
                    currentSong,
                    240
                )
            if (bitmap == null) {
                // Set default cover art
                views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
            } else {
                views.setImageViewBitmap(R.id.appwidget_coverart, bitmap)
            }
        } catch (all: Exception) {
            Timber.e(all, "Failed to load cover art")
            views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
        }

        // Link actions buttons to intents
        linkButtons(context, views, currentSong != null)
        pushUpdate(context, null, views)
    }

    companion object {
        /**
         * Link up various button actions using [PendingIntent].
         */
        private fun linkButtons(context: Context, views: RemoteViews, playerActive: Boolean) {
            var intent = Intent(
                context,
                NavigationActivity::class.java
            ).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
            if (playerActive) intent.putExtra(Constants.INTENT_SHOW_PLAYER, true)
            intent.action = "android.intent.action.MAIN"
            intent.addCategory("android.intent.category.LAUNCHER")
            var flags = PendingIntent.FLAG_UPDATE_CURRENT
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // needed starting Android 12 (S = 31)
                flags = flags or PendingIntent.FLAG_IMMUTABLE
            }
            var pendingIntent =
                PendingIntent.getActivity(context, 10, intent, flags)
            views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent)
            views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent)

            // Emulate media button clicks.
            intent = Intent(Constants.CMD_PROCESS_KEYCODE)
            intent.component = ComponentName(context, MediaButtonIntentReceiver::class.java)
            intent.putExtra(
                Intent.EXTRA_KEY_EVENT,
                KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)
            )
            flags = 0
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // needed starting Android 12 (S = 31)
                flags = flags or PendingIntent.FLAG_IMMUTABLE
            }
            pendingIntent = PendingIntent.getBroadcast(context, 11, intent, flags)
            views.setOnClickPendingIntent(R.id.control_play, pendingIntent)
            intent = Intent(Constants.CMD_PROCESS_KEYCODE)
            intent.component = ComponentName(context, MediaButtonIntentReceiver::class.java)
            intent.putExtra(
                Intent.EXTRA_KEY_EVENT,
                KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT)
            )
            pendingIntent = PendingIntent.getBroadcast(context, 12, intent, flags)
            views.setOnClickPendingIntent(R.id.control_next, pendingIntent)
            intent = Intent(Constants.CMD_PROCESS_KEYCODE)
            intent.component = ComponentName(context, MediaButtonIntentReceiver::class.java)
            intent.putExtra(
                Intent.EXTRA_KEY_EVENT,
                KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS)
            )
            pendingIntent = PendingIntent.getBroadcast(context, 13, intent, flags)
            views.setOnClickPendingIntent(R.id.control_previous, pendingIntent)
        }
    }
}
