/*
 * 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.settings.spa.app.appcompat

import android.app.settings.SettingsEnums
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.GET_ACTIVITIES
import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT
import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.applications.appcompat.UserAspectRatioManager
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.rememberContext
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.asyncMap
import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spa.widget.illustration.Illustration
import com.android.settingslib.spa.widget.illustration.IllustrationModel
import com.android.settingslib.spa.widget.illustration.ResourceType
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.userId
import com.android.settingslib.spaprivileged.template.app.AppList
import com.android.settingslib.spaprivileged.template.app.AppListInput
import com.android.settingslib.spaprivileged.template.app.AppListItem
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
import com.android.settingslib.spaprivileged.template.app.AppListPage
import com.google.common.annotations.VisibleForTesting
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn

object UserAspectRatioAppsPageProvider : SettingsPageProvider {
    override val name = "UserAspectRatioAppsPage"
    private val owner = createSettingsPage()

    override fun isEnabled(arguments: Bundle?): Boolean =
        UserAspectRatioManager.isFeatureEnabled(SpaEnvironmentFactory.instance.appContext)

    @Composable
    override fun Page(arguments: Bundle?) =
        UserAspectRatioAppList()

    @Composable
    @VisibleForTesting
    fun EntryItem() {
        val summary = getSummary()
        Preference(object : PreferenceModel {
            override val title = stringResource(R.string.aspect_ratio_experimental_title)
            override val summary = { summary }
            override val onClick = navigator(name)
        })
    }

    @VisibleForTesting
    fun buildInjectEntry() = SettingsEntryBuilder
        .createInject(owner)
        .setSearchDataFn { null }
        .setUiLayoutFn { EntryItem() }

    @Composable
    @VisibleForTesting
    fun getSummary(): String = stringResource(R.string.aspect_ratio_summary_text, Build.MODEL)
}

@Composable
fun UserAspectRatioAppList(
    appList: @Composable AppListInput<UserAspectRatioAppListItemModel>.() -> Unit
    = { AppList() },
) {
    AppListPage(
        title = stringResource(R.string.aspect_ratio_experimental_title),
        listModel = rememberContext(::UserAspectRatioAppListModel),
        appList = appList,
        header = {
            Box(Modifier.padding(SettingsDimension.itemPadding)) {
                SettingsBody(stringResource(R.string.aspect_ratio_main_summary_text, Build.MODEL))
            }
            Illustration(object : IllustrationModel {
                override val resId = R.raw.user_aspect_ratio_education
                override val resourceType = ResourceType.LOTTIE
            })
        },
        noMoreOptions = true,
    )
}

data class UserAspectRatioAppListItemModel(
    override val app: ApplicationInfo,
    val userOverride: Int,
    val suggested: Boolean,
    val canDisplay: Boolean,
) : AppRecord

class UserAspectRatioAppListModel(
    private val context: Context,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : AppListModel<UserAspectRatioAppListItemModel> {

    private val packageManager = context.packageManager
    private val userAspectRatioManager = UserAspectRatioManager(context)

    override fun getSpinnerOptions(
        recordList: List<UserAspectRatioAppListItemModel>
    ): List<SpinnerOption> {
        val hasSuggested = recordList.any { it.suggested }
        val hasOverride = recordList.any {
            userAspectRatioManager.isAppOverridden(it.app, it.userOverride)
        }
        val options = mutableListOf(SpinnerItem.All)
        // Add suggested filter first as default
        if (hasSuggested) options.add(0, SpinnerItem.Suggested)
        if (hasOverride) options += SpinnerItem.Overridden
        return options.map {
            SpinnerOption(
                id = it.ordinal,
                text = context.getString(it.stringResId),
            )
        }
    }

    @Composable
    override fun AppListItemModel<UserAspectRatioAppListItemModel>.AppItem() {
        val app = record.app
        AppListItem(
            onClick = {
                navigateToAppAspectRatioSettings(
                    context,
                    app,
                    SettingsEnums.USER_ASPECT_RATIO_APP_LIST_SETTINGS
                )
            }
        )
    }

    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
        userIdFlow.combine(appListFlow) { uid, appList ->
            appList.asyncMap { app ->
                UserAspectRatioAppListItemModel(
                    app = app,
                    suggested = !app.isSystemApp && getPackageAndActivityInfo(
                                    app)?.isFixedOrientationOrAspectRatio() == true,
                    userOverride = userAspectRatioManager.getUserMinAspectRatioValue(
                                    app.packageName, uid),
                    canDisplay = userAspectRatioManager.canDisplayAspectRatioUi(app),
                )
            }
        }

    override fun filter(
        userIdFlow: Flow<Int>,
        option: Int,
        recordListFlow: Flow<List<UserAspectRatioAppListItemModel>>
    ): Flow<List<UserAspectRatioAppListItemModel>> = recordListFlow.filterItem(
        when (SpinnerItem.entries.getOrNull(option)) {
            SpinnerItem.Suggested -> ({ it.canDisplay && it.suggested })
            SpinnerItem.Overridden -> ({
                userAspectRatioManager.isAppOverridden(it.app, it.userOverride)
            })
            else -> ({ it.canDisplay })
        }
    )

    @Composable
    override fun getSummary(option: Int, record: UserAspectRatioAppListItemModel): () -> String {
        val summary by remember(record.userOverride) {
            flow {
                emit(userAspectRatioManager.getUserMinAspectRatioEntry(record.userOverride,
                    record.app.packageName, record.app.userId))
            }.flowOn(ioDispatcher)
        }.collectAsStateWithLifecycle(initialValue = stringResource(R.string.summary_placeholder))
        return { summary }
    }

    private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try {
        packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
    } catch (e: Exception) {
        // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause
        // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture
        // packages, we need to catch the exception to alleviate the impact before PackageManager
        // fixing this issue or provide a better api.
        Log.e(TAG, "Exception while getPackageInfoAsUser", e)
        null
    }

    companion object {
        private const val TAG = "AspectRatioAppsListModel"
        private fun PackageInfo.isFixedOrientationOrAspectRatio() =
            activities?.any { a -> a.isFixedOrientation || a.hasFixedAspectRatio() } ?: false
        private val GET_ACTIVITIES_FLAGS =
            PackageManager.PackageInfoFlags.of(GET_ACTIVITIES.toLong())
    }
}

private enum class SpinnerItem(val stringResId: Int) {
    Suggested(R.string.user_aspect_ratio_suggested_apps_label),
    All(R.string.filter_all_apps),
    Overridden(R.string.user_aspect_ratio_changed_apps_label)
}