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

package org.moire.ultrasonic.service

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.support.v4.media.session.MediaSessionCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import org.koin.android.ext.android.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.util.SimpleServiceBinder
import org.moire.ultrasonic.util.Util
import timber.log.Timber

/**
 * Android Foreground service which is used to download tracks even when the app is not visible
 *
 * "A foreground service is a service that the user is
 * actively aware of and isn’t a candidate for the system to kill when low on memory."
 *
 * TODO: Migrate this to use the Media3 DownloadHelper
 */
class DownloadService : Service() {
    private val binder: IBinder = SimpleServiceBinder(this)

    private val downloader by inject<Downloader>()

    private var mediaSession: MediaSessionCompat? = null

    private var isInForeground = false

    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    override fun onCreate() {
        super.onCreate()

        // Create Notification Channel
        createNotificationChannel()
        updateNotification()

        instance = this
        startedSemaphore.release()
        Timber.i("DownloadService initiated")
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        instance = null
        try {
            downloader.stop()

            mediaSession?.release()
            mediaSession = null
        } catch (ignored: Throwable) {
        }
        Timber.i("DownloadService destroyed")
    }

    fun notifyDownloaderStopped() {
        Timber.i("DownloadService stopped")
        isInForeground = false
        stopForeground(true)
        stopSelf()
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            // The suggested importance of a startForeground service notification is IMPORTANCE_LOW
            val channel = NotificationChannel(
                NOTIFICATION_CHANNEL_ID,
                NOTIFICATION_CHANNEL_NAME,
                NotificationManager.IMPORTANCE_LOW
            )

            channel.lightColor = android.R.color.holo_blue_dark
            channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            channel.setShowBadge(false)

            val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
        }
    }

    // We should use a single notification builder, otherwise the notification may not be updated
    // Set some values that never change
    private val notificationBuilder: NotificationCompat.Builder by lazy {
        NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_stat_ultrasonic)
            .setAutoCancel(false)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .setWhen(System.currentTimeMillis())
            .setShowWhen(false)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setContentIntent(Util.getPendingIntentToShowPlayer(this))
            .setPriority(NotificationCompat.PRIORITY_LOW)
    }

    private fun updateNotification() {

        val notification = buildForegroundNotification()

        if (isInForeground) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
                manager.notify(NOTIFICATION_ID, notification)
            } else {
                val manager = NotificationManagerCompat.from(this)
                manager.notify(NOTIFICATION_ID, notification)
            }
            Timber.v("Updated notification")
        } else {
            startForeground(NOTIFICATION_ID, notification)
            isInForeground = true
            Timber.v("Created Foreground notification")
        }
    }

    /**
     * This method builds a notification, reusing the Notification Builder if possible
     */
    @Suppress("SpreadOperator")
    private fun buildForegroundNotification(): Notification {

        if (downloader.started) {
            // No song is playing, but Ultrasonic is downloading files
            notificationBuilder.setContentTitle(
                getString(R.string.notification_downloading_title)
            )
        }

        return notificationBuilder.build()
    }

    @Suppress("MagicNumber")
    companion object {

        private const val NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic"
        private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service"
        private const val NOTIFICATION_ID = 3033

        @Volatile
        private var instance: DownloadService? = null
        private val instanceLock = Any()
        private val startedSemaphore: Semaphore = Semaphore(0)

        @JvmStatic
        fun getInstance(): DownloadService? {
            val context = UApp.applicationContext()
            if (instance != null) return instance
            synchronized(instanceLock) {
                if (instance != null) return instance
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    context.startForegroundService(
                        Intent(context, DownloadService::class.java)
                    )
                } else {
                    context.startService(Intent(context, DownloadService::class.java))
                }
                Timber.i("DownloadService starting...")
                if (startedSemaphore.tryAcquire(10, TimeUnit.SECONDS)) {
                    Timber.i("DownloadService started")
                    return instance
                }
                Timber.w("DownloadService failed to start!")
                return null
            }
        }

        @JvmStatic
        val runningInstance: DownloadService?
            get() {
                synchronized(instanceLock) { return instance }
            }

        @JvmStatic
        fun executeOnStartedDownloadService(
            taskToExecute: (DownloadService) -> Unit
        ) {

            val t: Thread = object : Thread() {
                override fun run() {
                    val instance = getInstance()
                    if (instance == null) {
                        Timber.e("ExecuteOnStarted.. failed to get a DownloadService instance!")
                        return
                    } else {
                        taskToExecute(instance)
                    }
                }
            }
            t.start()
        }
    }
}
