/*
 * Nextcloud Android client application
 *
 * @author Chris Narkiewicz
 * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.nextcloud.client.files.downloader

import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import com.nextcloud.client.account.User
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.core.LocalBinder
import com.nextcloud.client.logger.Logger
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.notifications.AppNotificationManager
import dagger.android.AndroidInjection
import javax.inject.Inject
import javax.inject.Named

class DownloaderService : Service() {

    companion object {
        const val TAG = "DownloaderService"
        const val ACTION_DOWNLOAD = "download"
        const val EXTRA_REQUEST = "request"
        const val EXTRA_USER = "user"

        fun createBindIntent(context: Context, user: User): Intent {
            return Intent(context, DownloaderService::class.java).apply {
                putExtra(EXTRA_USER, user)
            }
        }

        fun createDownloadIntent(context: Context, request: Request): Intent {
            return Intent(context, DownloaderService::class.java).apply {
                action = ACTION_DOWNLOAD
                putExtra(EXTRA_REQUEST, request)
            }
        }
    }

    /**
     * Binder forwards [Downloader] API calls to selected instance of downloader.
     */
    class Binder(
        downloader: DownloaderImpl,
        service: DownloaderService
    ) : LocalBinder<DownloaderService>(service),
        Downloader by downloader

    @Inject
    lateinit var notificationsManager: AppNotificationManager

    @Inject
    lateinit var clientFactory: ClientFactory

    @Inject
    @Named("io")
    lateinit var runner: AsyncRunner

    @Inject
    lateinit var logger: Logger

    val isRunning: Boolean get() = downloaders.any { it.value.isRunning }

    private val downloaders: MutableMap<String, DownloaderImpl> = mutableMapOf()

    override fun onCreate() {
        AndroidInjection.inject(this)
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        if (intent.action != ACTION_DOWNLOAD) {
            return START_NOT_STICKY
        }

        if (!isRunning) {
            startForeground(
                AppNotificationManager.DOWNLOAD_NOTIFICATION_ID,
                notificationsManager.buildDownloadServiceForegroundNotification()
            )
        }

        val request = intent.getParcelableExtra(EXTRA_REQUEST) as Request
        val downloader = getDownloader(request.user)
        downloader.download(request)

        logger.d(TAG, "Enqueued new download: ${request.uuid} ${request.file.remotePath}")

        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        val user = intent?.getParcelableExtra<User>(EXTRA_USER)
        if (user != null) {
            return Binder(getDownloader(user), this)
        } else {
            return null
        }
    }

    private fun onDownloadUpdate(download: Download) {
        if (!isRunning) {
            logger.d(TAG, "All downloads completed")
            notificationsManager.cancelDownloadProgress()
            stopForeground(true)
            stopSelf()
        } else {
            notificationsManager.postDownloadProgress(
                fileOwner = download.request.user,
                file = download.request.file,
                progress = download.progress,
                allowPreview = !download.request.test
            )
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        logger.d(TAG, "Stopping downloader service")
    }

    private fun getDownloader(user: User): DownloaderImpl {
        val existingDownloader = downloaders[user.accountName]
        return if (existingDownloader != null) {
            existingDownloader
        } else {
            val downloadTaskFactory = DownloadTask.Factory(
                applicationContext,
                { clientFactory.create(user) },
                contentResolver
            )
            val newDownloader = DownloaderImpl(runner, downloadTaskFactory)
            newDownloader.registerDownloadListener(this::onDownloadUpdate)
            downloaders[user.accountName] = newDownloader
            newDownloader
        }
    }
}
