/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.perf

import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.GleanMetrics.Metrics
import org.mozilla.fenix.android.DefaultActivityLifecycleCallbacks

private val logger = Logger("AppStartReasonProvider")

/**
 * Provides the reason this [Application] instance was started: see [StartReason] for options
 * and [reason] for details.
 *
 * [registerInAppOnCreate] must be called for this class to work correctly.
 *
 * This class relies on specific lifecycle method call orders and main thread Runnable scheduling
 * that could potentially change between OEMs and OS versions: **be careful when using it.** This
 * implementation was tested on the Moto G5 Android 8.1.0 and the Pixel 2 Android 11.
 */
class AppStartReasonProvider {

    enum class StartReason {
        /** We don't know yet what caused this [Application] instance to be started. */
        TO_BE_DETERMINED,

        /** This [Application] instance was started due to an Activity trying to start. */
        ACTIVITY,

        /**
         * This [Application] instance was started due to a component that is not an Activity:
         * this may include Services, BroadcastReceivers, and ContentProviders. It may be possible
         * to distinguish between these but it hasn't been necessary to do so yet.
         */
        NON_ACTIVITY,
    }

    /**
     * The reason this [Application] instance was started. This will not be set immediately
     * but is expected to be available by the time the first frame is drawn for the foreground Activity.
     */
    var reason = StartReason.TO_BE_DETERMINED
        private set

    /**
     * Registers the handlers needed by this class: this is expected to be called from
     * [Application.onCreate].
     */
    fun registerInAppOnCreate(application: Application) {
        ProcessLifecycleOwner.get().lifecycle.addObserver(ProcessLifecycleObserver())
        application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks())
    }

    private inner class ProcessLifecycleObserver : DefaultLifecycleObserver {
        override fun onCreate(owner: LifecycleOwner) {
            Handler(Looper.getMainLooper()).post {
                // If the Application was started by an Activity, this Runnable should execute
                // after we learn the Activity was created. If the App was started by a Service,
                // this Runnable should execute before the first Activity is created.
                reason = when (reason) {
                    StartReason.TO_BE_DETERMINED -> StartReason.NON_ACTIVITY
                    StartReason.ACTIVITY -> reason /* the start reason is already known: do nothing. */
                    StartReason.NON_ACTIVITY -> {
                        Metrics.startReasonProcessError.set(true)
                        logger.error("AppStartReasonProvider.Process...onCreate unexpectedly called twice")
                        reason
                    }
                }
            }

            owner.lifecycle.removeObserver(this) // we don't update the state further.
        }
    }

    private inner class ActivityLifecycleCallbacks : DefaultActivityLifecycleCallbacks {
        override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
            // See ProcessLifecycleObserver.onCreate for details.
            reason = when (reason) {
                StartReason.TO_BE_DETERMINED -> StartReason.ACTIVITY
                StartReason.NON_ACTIVITY -> reason /* the start reason is already known: do nothing. */
                StartReason.ACTIVITY -> {
                    Metrics.startReasonActivityError.set(true)
                    logger.error("AppStartReasonProvider.Activity...onCreate unexpectedly called twice")
                    reason
                }
            }

            activity.application.unregisterActivityLifecycleCallbacks(this) // we don't update the state further.
        }
    }
}
