/*
 * Copyright (C) 2023 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.controls.start

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
import android.os.UserManager
import androidx.annotation.WorkerThread
import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject

/**
 * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
 *
 * In particular, it will perform the following:
 * * If there is no preferred selection for provider and at least one of the preferred packages
 * provides a panel, it will select the first one that does.
 * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
 * displaying the panel).
 *
 * It will also perform those operations on user change.
 */
@SysUISingleton
class ControlsStartable
@Inject
constructor(
        @Background private val executor: Executor,
        private val controlsComponent: ControlsComponent,
        private val userTracker: UserTracker,
        private val authorizedPanelsRepository: AuthorizedPanelsRepository,
        private val selectedComponentRepository: SelectedComponentRepository,
        private val userManager: UserManager,
        private val broadcastDispatcher: BroadcastDispatcher,
) : CoreStartable {

    // These two controllers can only be accessed after `start` method once we've checked if the
    // feature is enabled
    private val controlsController: ControlsController
        get() = controlsComponent.getControlsController().get()

    private val controlsListingController: ControlsListingController
        get() = controlsComponent.getControlsListingController().get()

    private val userTrackerCallback =
        object : UserTracker.Callback {
            override fun onUserChanged(newUser: Int, userContext: Context) {
                controlsController.changeUser(UserHandle.of(newUser))
                startForUser()
            }
        }

    override fun start() {}

    override fun onBootCompleted() {
        if (!controlsComponent.isEnabled()) {
            // Controls is disabled, we don't need this anymore
            return
        }
        executor.execute(this::startForUser)
        userTracker.addCallback(userTrackerCallback, executor)
    }

    @WorkerThread
    private fun startForUser() {
        controlsListingController.forceReload()
        selectDefaultPanelIfNecessary()
        bindToPanel()
    }

    private fun selectDefaultPanelIfNecessary() {
        if (!selectedComponentRepository.shouldAddDefaultComponent()) {
            return
        }
        val currentSelection = controlsController.getPreferredSelection()
        if (currentSelection == SelectedItem.EMPTY_SELECTION) {
            val availableServices = controlsListingController.getCurrentServices()
            val panels = availableServices.filter { it.panelActivity != null }
            authorizedPanelsRepository
                .getPreferredPackages()
                // Looking for the first element in the string array such that there is one package
                // that has a panel. It will return null if there are no packages in the array,
                // or if no packages in the array have a panel associated with it.
                .firstNotNullOfOrNull { name ->
                    panels.firstOrNull { it.componentName.packageName == name }
                }
                ?.let { info ->
                    controlsController.setPreferredSelection(
                        SelectedItem.PanelItem(info.loadLabel(), info.componentName)
                    )
                }
        }
    }

    private fun bindToPanel() {
        if (userManager.isUserUnlocked(userTracker.userId)) {
            bindToPanelInternal()
        } else {
            broadcastDispatcher.registerReceiver(
                    receiver = object : BroadcastReceiver() {
                        override fun onReceive(context: Context?, intent: Intent?) {
                            if (userManager.isUserUnlocked(userTracker.userId)) {
                                bindToPanelInternal()
                                broadcastDispatcher.unregisterReceiver(this)
                            }
                        }
                    },
                    filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
                    executor = executor,
                    user = userTracker.userHandle,
            )
        }
    }

    private fun bindToPanelInternal() {
        val currentSelection = controlsController.getPreferredSelection()
        val panels =
                controlsListingController.getCurrentServices().filter { it.panelActivity != null }
        if (currentSelection is SelectedItem.PanelItem &&
                panels.firstOrNull { it.componentName == currentSelection.componentName } != null
        ) {
            controlsController.bindComponentForPanel(currentSelection.componentName)
        }
    }
}
