/*
 * Copyright (C) 2021 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.statusbar.policy

import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import android.util.Log

import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.QSTileHost.POSITION_AT_END
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.policy.DeviceControlsController.Callback

import javax.inject.Inject

/**
 * Watches for Device Controls QS Tile activation, which can happen in two ways:
 * <ol>
 *   <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high
 *       priority position.
 *   <li>Device controls service becomes available - For non-migrated users, create a tile and
 *       place at the end of active tiles, and initiate seeding where possible.
 * </ol>
 */
@SysUISingleton
public class DeviceControlsControllerImpl @Inject constructor(
    private val context: Context,
    private val controlsComponent: ControlsComponent,
    private val userContextProvider: UserContextProvider
) : DeviceControlsController {

    private var callback: Callback? = null
    internal var position: Int? = null

    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
            if (!serviceInfos.isEmpty()) {
                seedFavorites(serviceInfos)
            }
        }
    }

    companion object {
        private const val TAG = "DeviceControlsControllerImpl"
        internal const val QS_PRIORITY_POSITION = 3
        internal const val QS_DEFAULT_POSITION = POSITION_AT_END

        internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
        internal const val PREFS_CONTROLS_FILE = "controls_prefs"
        private const val SEEDING_MAX = 2
    }

    private fun checkMigrationToQs() {
        controlsComponent.getControlsController().ifPresent {
            if (!it.getFavorites().isEmpty()) {
                position = QS_PRIORITY_POSITION
            }
        }
    }

    /**
     * This migration logic assumes that something like [AutoTileManager] is tracking state
     * externally, and won't call this method after receiving a response via
     * [Callback#onControlsAvailable], once per user. Otherwise the calculated position may be
     * incorrect.
     */
    override fun setCallback(callback: Callback) {
        // Treat any additional call as a reset before recalculating
        removeCallback()

        checkMigrationToQs()
        controlsComponent.getControlsListingController().ifPresent {
            it.addCallback(listingCallback)
        }

        this.callback = callback
        fireControlsAvailable()
    }

    override fun removeCallback() {
        position = null
        callback = null
        controlsComponent.getControlsListingController().ifPresent {
            it.removeCallback(listingCallback)
        }
    }

    private fun fireControlsAvailable() {
        position?.let {
            Log.i(TAG, "Setting DeviceControlsTile position: $it")
            callback?.onControlsAvailable(it)
        }
    }

    /**
     * See if any available control service providers match one of the preferred components. If
     * they do, and there are no current favorites for that component, query the preferred
     * component for a limited number of suggested controls.
     */
    private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) {
        val preferredControlsPackages = context.getResources().getStringArray(
            R.array.config_controlsPreferredPackages)

        val prefs = userContextProvider.userContext.getSharedPreferences(
            PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())

        val controlsController = controlsComponent.getControlsController().get()
        val componentsToSeed = mutableListOf<ComponentName>()
        var i = 0
        while (i < Math.min(SEEDING_MAX, preferredControlsPackages.size)) {
            val pkg = preferredControlsPackages[i]
            serviceInfos.forEach {
                if (pkg.equals(it.componentName.packageName) && !seededPackages.contains(pkg)) {
                    if (controlsController.countFavoritesForComponent(it.componentName) > 0) {
                        // When there are existing controls but no saved preference, assume it
                        // is out of sync, perhaps through a device restore, and update the
                        // preference
                        addPackageToSeededSet(prefs, pkg)
                    } else {
                        componentsToSeed.add(it.componentName)
                    }
                }
            }
            i++
        }

        if (componentsToSeed.isEmpty()) return

        controlsController.seedFavoritesForComponents(
                componentsToSeed,
                { response ->
                    Log.d(TAG, "Controls seeded: $response")
                    if (response.accepted) {
                        addPackageToSeededSet(prefs, response.packageName)
                        if (position == null) {
                            position = QS_DEFAULT_POSITION
                        }
                        fireControlsAvailable()

                        controlsComponent.getControlsListingController().ifPresent {
                            it.removeCallback(listingCallback)
                        }
                    }
                })
    }

    private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
        val updatedPkgs = seededPackages.toMutableSet()
        updatedPkgs.add(pkg)
        prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
    }
}
