/*
 * Copyright (C) 2022 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.mediaprojection.appselector

import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.os.UserHandle
import androidx.lifecycle.DefaultLifecycleObserver
import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProvider
import com.android.systemui.mediaprojection.appselector.view.WindowMetricsProviderImpl
import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Binds
import dagger.BindsInstance
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import javax.inject.Qualifier
import javax.inject.Scope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle

@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUid

@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope

@Module(
    subcomponents = [MediaProjectionAppSelectorComponent::class],
    includes = [MediaProjectionDevicePolicyModule::class]
)
interface MediaProjectionModule {
    @Binds
    @IntoMap
    @ClassKey(MediaProjectionAppSelectorActivity::class)
    fun provideMediaProjectionAppSelectorActivity(
        activity: MediaProjectionAppSelectorActivity
    ): Activity

    @Binds
    @IntoMap
    @ClassKey(MediaProjectionPermissionActivity::class)
    fun bindsMediaProjectionPermissionActivity(impl: MediaProjectionPermissionActivity): Activity
}

/**
 * Scoped values for [MediaProjectionAppSelectorComponent]. We create a scope for the activity so
 * certain dependencies like [TaskPreviewSizeProvider] could be reused.
 */
@Module
interface MediaProjectionAppSelectorModule {

    @Binds
    @MediaProjectionAppSelectorScope
    fun bindRecentTaskThumbnailLoader(
        impl: ActivityTaskManagerThumbnailLoader
    ): RecentTaskThumbnailLoader

    @Binds
    @MediaProjectionAppSelectorScope
    fun bindRecentTaskLabelLoader(impl: ActivityTaskManagerLabelLoader): RecentTaskLabelLoader

    @Binds
    @MediaProjectionAppSelectorScope
    fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider

    @Binds
    @MediaProjectionAppSelectorScope
    fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader

    @Binds
    @IntoSet
    fun taskPreviewSizeProviderAsLifecycleObserver(
        impl: TaskPreviewSizeProvider
    ): DefaultLifecycleObserver

    @Binds fun windowMetricsProvider(impl: WindowMetricsProviderImpl): WindowMetricsProvider

    companion object {
        @Provides
        @MediaProjectionAppSelector
        @MediaProjectionAppSelectorScope
        fun provideAppSelectorComponentName(context: Context): ComponentName =
            ComponentName(context, MediaProjectionAppSelectorActivity::class.java)

        @Provides
        @MediaProjectionAppSelector
        @MediaProjectionAppSelectorScope
        fun bindConfigurationController(context: Context): ConfigurationController =
            ConfigurationControllerImpl(context)

        @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)

        @Provides
        @MediaProjectionAppSelector
        @MediaProjectionAppSelectorScope
        fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
            CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
    }
}

@Subcomponent(modules = [MediaProjectionAppSelectorModule::class])
@MediaProjectionAppSelectorScope
interface MediaProjectionAppSelectorComponent {

    /** Generates [MediaProjectionAppSelectorComponent]. */
    @Subcomponent.Factory
    interface Factory {
        /** Create a factory to inject the activity into the graph */
        fun create(
            @BindsInstance @HostUserHandle hostUserHandle: UserHandle,
            @BindsInstance @HostUid hostUid: Int,
            @BindsInstance @MediaProjectionAppSelector callingPackage: String?,
            @BindsInstance view: MediaProjectionAppSelectorView,
            @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
            // Whether the app selector is starting for the first time. False when it is re-starting
            // due to a config change.
            @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,
        ): MediaProjectionAppSelectorComponent
    }

    val controller: MediaProjectionAppSelectorController
    val recentsViewController: MediaProjectionRecentsViewController
    val emptyStateProvider: MediaProjectionBlockerEmptyStateProvider
    @get:HostUserHandle val hostUserHandle: UserHandle
    @get:PersonalProfile val personalProfileUserHandle: UserHandle

    @MediaProjectionAppSelector val configurationController: ConfigurationController
    val lifecycleObservers: Set<DefaultLifecycleObserver>
}
