/*
 * Copyright (C) 2020 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.statusbar.notification.collection

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
import javax.inject.Inject

/**
 * The system that constructs the "shade list", the filtered, grouped, and sorted list of
 * notifications that are currently being displayed to the user in the notification shade.
 *
 * The pipeline proceeds through a series of stages in order to produce the final list (see below).
 * Each stage exposes hooks and listeners to allow other code to participate.
 *
 * This list differs from the canonical one we receive from system server in a few ways:
 * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
 *   views haven't been inflated yet. We also filter out some notifications if we're on the lock
 *   screen and notifications for other users. To participate, see
 *   [.addPreGroupFilter] and similar methods.
 * - Grouped: Notifications that are part of the same group are clustered together into a single
 *   GroupEntry. These groups are then transformed in order to remove children or completely split
 *   them apart. To participate, see [.addPromoter].
 * - Sorted: All top-level notifications are sorted. To participate, see
 *   [.setSections] and [.setComparators]
 *
 * The exact order of all hooks is as follows:
 *  0. Collection listeners are fired ([.addCollectionListener]).
 *  1. Pre-group filters are fired on each notification ([.addPreGroupFilter]).
 *  2. Initial grouping is performed (NotificationEntries will have their parents set
 *     appropriately).
 *  3. OnBeforeTransformGroupListeners are fired ([.addOnBeforeTransformGroupsListener])
 *  4. NotifPromoters are called on each notification with a parent ([.addPromoter])
 *  5. OnBeforeSortListeners are fired ([.addOnBeforeSortListener])
 *  6. Top-level entries are assigned sections by NotifSections ([.setSections])
 *  7. Top-level entries within the same section are sorted by NotifComparators ([.setComparators])
 *  8. OnBeforeFinalizeFilterListeners are fired ([.addOnBeforeFinalizeFilterListener])
 *  9. Finalize filters are fired on each notification ([.addFinalizeFilter])
 *  10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener])
 *  11. The list is handed off to the view layer to be rendered
 *  12. OnAfterRenderListListeners are fired ([.addOnAfterRenderListListener])
 *  13. OnAfterRenderGroupListeners are fired ([.addOnAfterRenderGroupListener])
 *  13. OnAfterRenderEntryListeners are fired ([.addOnAfterRenderEntryListener])
 */
@SysUISingleton
class NotifPipeline @Inject constructor(
    private val mNotifCollection: NotifCollection,
    private val mShadeListBuilder: ShadeListBuilder,
    private val mRenderStageManager: RenderStageManager
) : CommonNotifCollection {
    /**
     * Returns the list of all known notifications, i.e. the notifications that are currently posted
     * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
     * but it can diverge slightly due to lifetime extenders.
     *
     * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
     */
    override fun getAllNotifs(): Collection<NotificationEntry> {
        return mNotifCollection.allNotifs
    }

    override fun addCollectionListener(listener: NotifCollectionListener) {
        mNotifCollection.addCollectionListener(listener)
    }

    override fun removeCollectionListener(listener: NotifCollectionListener) {
        mNotifCollection.removeCollectionListener(listener)
    }

    /**
     * Returns the NotificationEntry associated with [key].
     */
    override fun getEntry(key: String): NotificationEntry? {
        return mNotifCollection.getEntry(key)
    }

    /**
     * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
     * dismissed or retracted by system server to be temporarily retained in the collection.
     */
    fun addNotificationLifetimeExtender(extender: NotifLifetimeExtender) {
        mNotifCollection.addNotificationLifetimeExtender(extender)
    }

    /**
     * Registers a dismiss interceptor. Dismiss interceptors can cause notifications that have been
     * dismissed by the user to be retained (won't send a dismissal to system server).
     */
    fun addNotificationDismissInterceptor(interceptor: NotifDismissInterceptor) {
        mNotifCollection.addNotificationDismissInterceptor(interceptor)
    }

    /**
     * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
     * are called on each notification in the order that they were registered. If any filter
     * returns true, the notification is removed from the pipeline (and no other filters are
     * called on that notif).
     */
    fun addPreGroupFilter(filter: NotifFilter) {
        mShadeListBuilder.addPreGroupFilter(filter)
    }

    /**
     * Called after notifications have been filtered and after the initial grouping has been
     * performed but before NotifPromoters have had a chance to promote children out of groups.
     */
    fun addOnBeforeTransformGroupsListener(listener: OnBeforeTransformGroupsListener) {
        mShadeListBuilder.addOnBeforeTransformGroupsListener(listener)
    }

    /**
     * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
     * top-level, i.e. move a notification that would be a child of a group and make it appear
     * ungrouped. Promoters are called on each child notification in the order that they are
     * registered. If any promoter returns true, the notification is removed from the group (and no
     * other promoters are called on it).
     */
    fun addPromoter(promoter: NotifPromoter) {
        mShadeListBuilder.addPromoter(promoter)
    }

    /**
     * Called after notifs have been filtered and groups have been determined but before sections
     * have been determined or the notifs have been sorted.
     */
    fun addOnBeforeSortListener(listener: OnBeforeSortListener) {
        mShadeListBuilder.addOnBeforeSortListener(listener)
    }

    /**
     * Sections that are used to sort top-level entries.  If two entries have the same section,
     * NotifComparators are consulted. Sections from this list are called in order for each
     * notification passed through the pipeline. The first NotifSection to return true for
     * [NotifSectioner.isInSection] sets the entry as part of its Section.
     */
    fun setSections(sections: List<NotifSectioner>) {
        mShadeListBuilder.setSectioners(sections)
    }

    /**
     * StabilityManager that is used to determine whether to suppress group and section changes.
     * This should only be set once.
     */
    fun setVisualStabilityManager(notifStabilityManager: NotifStabilityManager) {
        mShadeListBuilder.setNotifStabilityManager(notifStabilityManager)
    }

    /**
     * Comparators that are used to sort top-level entries that share the same section. The
     * comparators are executed in order until one of them returns a non-zero result. If all return
     * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
     */
    fun setComparators(comparators: List<NotifComparator>) {
        mShadeListBuilder.setComparators(comparators)
    }

    /**
     * Called after notifs have been filtered once, grouped, and sorted but before the final
     * filtering.
     */
    fun addOnBeforeFinalizeFilterListener(listener: OnBeforeFinalizeFilterListener) {
        mShadeListBuilder.addOnBeforeFinalizeFilterListener(listener)
    }

    /**
     * Registers a filter with the pipeline to filter right before rendering the list (after
     * pre-group filtering, grouping, promoting and sorting occurs). Filters are
     * called on each notification in the order that they were registered. If any filter returns
     * true, the notification is removed from the pipeline (and no other filters are called on that
     * notif).
     */
    fun addFinalizeFilter(filter: NotifFilter) {
        mShadeListBuilder.addFinalizeFilter(filter)
    }

    /**
     * Called at the end of the pipeline after the notif list has been finalized but before it has
     * been handed off to the view layer.
     */
    fun addOnBeforeRenderListListener(listener: OnBeforeRenderListListener) {
        mShadeListBuilder.addOnBeforeRenderListListener(listener)
    }

    /** Registers an invalidator that can be used to invalidate the entire notif list. */
    fun addPreRenderInvalidator(invalidator: Invalidator) {
        mShadeListBuilder.addPreRenderInvalidator(invalidator)
    }

    /**
     * Called at the end of the pipeline after the notif list has been handed off to the view layer.
     */
    fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) {
        mRenderStageManager.addOnAfterRenderListListener(listener)
    }

    /**
     * Called at the end of the pipeline after a group has been handed off to the view layer.
     */
    fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) {
        mRenderStageManager.addOnAfterRenderGroupListener(listener)
    }

    /**
     * Called at the end of the pipeline after an entry has been handed off to the view layer.
     * This will be called for every top level entry, every group summary, and every group child.
     */
    fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) {
        mRenderStageManager.addOnAfterRenderEntryListener(listener)
    }

    /**
     * Get an object which can be used to update a notification (internally to the pipeline)
     * in response to a user action.
     *
     * @param name the name of the component that will update notifiations
     * @return an updater
     */
    fun getInternalNotifUpdater(name: String?): InternalNotifUpdater {
        return mNotifCollection.getInternalNotifUpdater(name)
    }
}
