/*
 * 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.screenshot

import android.annotation.UserIdInt
import android.app.ActivityTaskManager
import android.app.ActivityTaskManager.RootTaskInfo
import android.app.IActivityTaskManager
import android.app.WindowConfiguration
import android.app.WindowConfiguration.activityTypeToString
import android.app.WindowConfiguration.windowingModeToString
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.os.Process
import android.os.RemoteException
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.infra.ServiceConnector
import com.android.systemui.SystemUIService
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import java.util.Arrays
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext

@SysUISingleton
internal open class ScreenshotPolicyImpl @Inject constructor(
    context: Context,
    private val userMgr: UserManager,
    private val atmService: IActivityTaskManager,
    @Background val bgDispatcher: CoroutineDispatcher,
    private val displayTracker: DisplayTracker
) : ScreenshotPolicy {

    private val proxyConnector: ServiceConnector<IScreenshotProxy> =
        ServiceConnector.Impl(
            context,
            Intent(context, ScreenshotProxyService::class.java),
            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
            context.userId,
            IScreenshotProxy.Stub::asInterface
        )

    override fun getDefaultDisplayId(): Int {
        return displayTracker.defaultDisplayId
    }

    override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
        val managed = withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
        Log.d(TAG, "isManagedProfile: $managed")
        return managed
    }

    private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
        if (DEBUG) {
            debugLogRootTaskInfo(info)
        }
        return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
            info.isVisible &&
            info.isRunning &&
            info.numActivities > 0 &&
            info.topActivity != null &&
            info.childTaskIds.isNotEmpty()
    }

    /**
     * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
     * display. If no task is visible or the top task is covered by a system window, the info
     * reported will reference a SystemUI component instead.
     */
    override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
        // Determine if the notification shade is expanded. If so, task windows are not
        // visible behind it, so the screenshot should instead be associated with SystemUI.
        if (isNotificationShadeExpanded()) {
            return systemUiContent
        }

        val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)

        // If no visible task is located, then report SystemUI as the foreground content
        val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
        return target.toDisplayContentInfo()
    }

    private fun debugLogRootTaskInfo(info: RootTaskInfo) {
        Log.d(TAG, "RootTaskInfo={" +
                "taskId=${info.taskId} " +
                "parentTaskId=${info.parentTaskId} " +
                "position=${info.position} " +
                "positionInParent=${info.positionInParent} " +
                "isVisible=${info.isVisible()} " +
                "visible=${info.visible} " +
                "isFocused=${info.isFocused} " +
                "isSleeping=${info.isSleeping} " +
                "isRunning=${info.isRunning} " +
                "windowMode=${windowingModeToString(info.windowingMode)} " +
                "activityType=${activityTypeToString(info.activityType)} " +
                "topActivity=${info.topActivity} " +
                "topActivityInfo=${info.topActivityInfo} " +
                "numActivities=${info.numActivities} " +
                "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
                "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
                "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
                "childTaskNames=${Arrays.toString(info.childTaskNames)}" +
                "}"
        )

        for (j in 0 until info.childTaskIds.size) {
            Log.d(TAG, "    *** [$j] ******")
            Log.d(TAG, "        ***  childTaskIds[$j]: ${info.childTaskIds[j]}")
            Log.d(TAG, "        ***  childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
            Log.d(TAG, "        ***  childTaskBounds[$j]: ${info.childTaskBounds[j]}")
            Log.d(TAG, "        ***  childTaskNames[$j]: ${info.childTaskNames[j]}")
        }
    }

    @VisibleForTesting
    open suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
        withContext(bgDispatcher) {
            try {
                atmService.getAllRootTaskInfosOnDisplay(displayId)
            } catch (e: RemoteException) {
                Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
                listOf()
            }
        }

    @VisibleForTesting
    open suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
        proxyConnector
            .postForResult { it.isNotificationShadeExpanded }
            .whenComplete { expanded, error ->
                if (error != null) {
                    Log.e(TAG, "isNotificationShadeExpanded", error)
                }
                k.resume(expanded ?: false)
            }
    }

    @VisibleForTesting
    internal val systemUiContent =
        DisplayContentInfo(
            ComponentName(context, SystemUIService::class.java),
            Rect(),
            Process.myUserHandle(),
            ActivityTaskManager.INVALID_TASK_ID
        )
}

private const val TAG: String = "ScreenshotPolicyImpl"
private const val DEBUG: Boolean = false

@VisibleForTesting
internal fun RootTaskInfo.toDisplayContentInfo(): DisplayContentInfo {
    val topActivity: ComponentName = topActivity ?: error("should not be null")
    val topChildTask = childTaskIds.size - 1
    val childTaskId = childTaskIds[topChildTask]
    val childTaskUserId = childTaskUserIds[topChildTask]
    val childTaskBounds = childTaskBounds[topChildTask]

    return DisplayContentInfo(
        topActivity,
        childTaskBounds,
        UserHandle.of(childTaskUserId),
        childTaskId)
}
