/*
 * Copyright (C) 2025 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.launcher3.model

import android.app.blob.BlobHandle
import android.app.blob.BlobStoreManager
import android.content.Context
import android.os.ParcelFileDescriptor.AutoCloseInputStream
import android.provider.Settings.Secure
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import android.util.Xml
import com.android.launcher3.AutoInstallsLayout
import com.android.launcher3.AutoInstallsLayout.SourceResources
import com.android.launcher3.DefaultLayoutParser
import com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT
import com.android.launcher3.LauncherSettings.Settings
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.util.IOUtils
import com.android.launcher3.util.Partner
import com.android.launcher3.widget.LauncherWidgetHolder
import java.io.StringReader
import javax.inject.Inject

private const val TAG = "LayoutParserFactory"

/** Utility class for providing default layout parsers */
open class LayoutParserFactory
@Inject
constructor(@ApplicationContext private val context: Context) {

    open fun createExternalLayoutParser(
        widgetHolder: LauncherWidgetHolder,
        openHelper: DatabaseHelper,
    ): AutoInstallsLayout? {

        createWorkspaceLoaderFromAppRestriction(widgetHolder, openHelper)?.let {
            return it
        }
        AutoInstallsLayout.get(context, widgetHolder, openHelper)?.let {
            return it
        }

        val partner = Partner.get(context.packageManager)
        if (partner != null) {
            val workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT)
            if (workspaceResId != 0) {
                return DefaultLayoutParser(
                    context,
                    widgetHolder,
                    openHelper,
                    partner.resources,
                    workspaceResId,
                )
            }
        }
        return null
    }

    /**
     * Creates workspace loader from an XML resource listed in the app restrictions.
     *
     * @return the loader if the restrictions are set and the resource exists; null otherwise.
     */
    private fun createWorkspaceLoaderFromAppRestriction(
        widgetHolder: LauncherWidgetHolder,
        openHelper: DatabaseHelper,
    ): AutoInstallsLayout? {
        val systemLayoutProvider =
            Secure.getString(context.contentResolver, Settings.LAYOUT_PROVIDER_KEY)
        if (TextUtils.isEmpty(systemLayoutProvider)) {
            return null
        }

        // Try the blob store first
        val blobManager = context.getSystemService(BlobStoreManager::class.java)
        if (systemLayoutProvider.startsWith(Settings.BLOB_KEY_PREFIX) && blobManager != null) {
            val blobHandlerDigest = systemLayoutProvider.substring(Settings.BLOB_KEY_PREFIX.length)
            try {
                AutoCloseInputStream(
                        blobManager.openBlob(
                            BlobHandle.createWithSha256(
                                Base64.decode(
                                    blobHandlerDigest,
                                    Base64.NO_WRAP or Base64.NO_PADDING,
                                ),
                                Settings.LAYOUT_DIGEST_LABEL,
                                0,
                                Settings.LAYOUT_DIGEST_TAG,
                            )
                        )
                    )
                    .use {
                        return getAutoInstallsLayoutFromIS(
                            widgetHolder,
                            openHelper,
                            String(IOUtils.toByteArray(it)),
                        )
                    }
            } catch (e: Exception) {
                Log.e(TAG, "Error getting layout from blob handle", e)
                return null
            }
        }

        // Try contentProvider based provider
        val pm = context.packageManager
        val pi = pm.resolveContentProvider(systemLayoutProvider, 0)
        if (pi == null) {
            Log.e(TAG, "No provider found for authority $systemLayoutProvider")
            return null
        }
        val uri = ModelDbController.getLayoutUri(systemLayoutProvider, context)
        try {
            context.contentResolver.openInputStream(uri)?.use {
                Log.d(TAG, "Loading layout from $systemLayoutProvider")
                val res = pm.getResourcesForApplication(pi.applicationInfo)
                return getAutoInstallsLayoutFromIS(
                    widgetHolder,
                    openHelper,
                    String(IOUtils.toByteArray(it)),
                    SourceResources.wrap(res),
                )
            }
        } catch (e: Exception) {
            Log.e(TAG, "Error getting layout stream from: $systemLayoutProvider", e)
        }
        return null
    }

    @Throws(Exception::class)
    protected fun getAutoInstallsLayoutFromIS(
        widgetHolder: LauncherWidgetHolder,
        openHelper: DatabaseHelper,
        xml: String,
        res: SourceResources = object : SourceResources {},
    ): AutoInstallsLayout {
        val parser = Xml.newPullParser()
        parser.setInput(StringReader(xml))

        return AutoInstallsLayout(
            context,
            widgetHolder,
            openHelper,
            res,
            { parser },
            AutoInstallsLayout.TAG_WORKSPACE,
        )
    }

    /** Layout parser factory with fixed xml */
    class XmlLayoutParserFactory(ctx: Context, private val xml: String) : LayoutParserFactory(ctx) {

        override fun createExternalLayoutParser(
            widgetHolder: LauncherWidgetHolder,
            openHelper: DatabaseHelper,
        ): AutoInstallsLayout? {
            try {
                return getAutoInstallsLayoutFromIS(widgetHolder, openHelper, xml)
            } catch (e: Exception) {
                Log.e(TAG, "Error getting layout from provided xml", e)
                return super.createExternalLayoutParser(widgetHolder, openHelper)
            }
        }
    }
}
