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

import com.android.systemui.log.dagger.NotificationLog
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.log.core.LogLevel.WARNING
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.StateName
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.getStateName
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.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.util.Compile
import javax.inject.Inject

class ShadeListBuilderLogger @Inject constructor(
    notifPipelineFlags: NotifPipelineFlags,
    @NotificationLog private val buffer: LogBuffer
) {
    fun logOnBuildList(reason: String?) {
        buffer.log(TAG, INFO, {
            str1 = reason
        }, {
            "Request received from NotifCollection for $str1"
        })
    }

    fun logEndBuildList(
        buildId: Int,
        topLevelEntries: Int,
        numChildren: Int,
        enforcedVisualStability: Boolean
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            int1 = topLevelEntries
            int2 = numChildren
            bool1 = enforcedVisualStability
        }, {
            "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" +
                    " enforcedVisualStability=$bool1"
        })
    }

    private fun logPluggableInvalidated(
        type: String,
        pluggable: Pluggable<*>,
        @StateName pipelineState: Int,
        reason: String?
    ) {
        buffer.log(TAG, DEBUG, {
            str1 = type
            str2 = pluggable.name
            int1 = pipelineState
            str3 = reason
        }, {
            """Invalidated while ${getStateName(int1)} by $str1 "$str2" because $str3"""
        })
    }

    fun logPreRenderInvalidated(
        invalidator: Invalidator,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("Pre-render Invalidator", invalidator, pipelineState, reason)

    fun logPreGroupFilterInvalidated(
        filter: NotifFilter,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("Pre-group NotifFilter", filter, pipelineState, reason)

    fun logReorderingAllowedInvalidated(
        stabilityManager: NotifStabilityManager,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("ReorderingNowAllowed", stabilityManager, pipelineState, reason)

    fun logPromoterInvalidated(
        promoter: NotifPromoter,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("NotifPromoter", promoter, pipelineState, reason)

    fun logNotifSectionInvalidated(
        sectioner: NotifSectioner,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("NotifSection", sectioner, pipelineState, reason)

    fun logNotifComparatorInvalidated(
        comparator: NotifComparator,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("NotifComparator", comparator, pipelineState, reason)

    fun logFinalizeFilterInvalidated(
        filter: NotifFilter,
        @StateName pipelineState: Int,
        reason: String?
    ) = logPluggableInvalidated("Finalize NotifFilter", filter, pipelineState, reason)

    fun logDuplicateSummary(
        buildId: Int,
        group: GroupEntry,
        existingSummary: NotificationEntry,
        newSummary: NotificationEntry
    ) {
        buffer.log(TAG, WARNING, {
            long1 = buildId.toLong()
            str1 = group.logKey
            str2 = existingSummary.logKey
            str3 = newSummary.logKey
        }, {
            """(Build $long1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
        })
    }

    fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
        buffer.log(TAG, WARNING, {
            long1 = buildId.toLong()
            str1 = logKey(topLevelKey)
        }, {
            "(Build $long1) Duplicate top-level key: $str1"
        })
    }

    fun logEntryAttachStateChanged(
        buildId: Int,
        entry: ListEntry,
        prevParent: GroupEntry?,
        newParent: GroupEntry?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = entry.logKey
            str2 = prevParent?.logKey
            str3 = newParent?.logKey
        }, {

            val action = if (str2 == null && str3 != null) {
                "ATTACHED"
            } else if (str2 != null && str3 == null) {
                "DETACHED"
            } else if (str2 == null && str3 == null) {
                "MODIFIED (DETACHED)"
            } else {
                "MODIFIED (ATTACHED)"
            }

            "(Build $long1) $action {$str1}"
        })
    }

    fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = prevParent?.logKey
            str2 = newParent?.logKey
        }, {
            if (str1 == null && str2 != null) {
                "(Build $long1)     Parent is {$str2}"
            } else if (str1 != null && str2 == null) {
                "(Build $long1)     Parent was {$str1}"
            } else {
                "(Build $long1)     Reparent: {$str1} -> {$str2}"
            }
        })
    }

    fun logParentChangeSuppressedStarted(
        buildId: Int,
        suppressedParent: GroupEntry?,
        keepingParent: GroupEntry?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = suppressedParent?.logKey
            str2 = keepingParent?.logKey
        }, {
            "(Build $long1)     Change of parent to '$str1' suppressed; keeping parent '$str2'"
        })
    }

    fun logParentChangeSuppressedStopped(
            buildId: Int,
            previouslySuppressedParent: GroupEntry?,
            previouslyKeptParent: GroupEntry?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = previouslySuppressedParent?.logKey
            str2 = previouslyKeptParent?.logKey
        }, {
            "(Build $long1)     Change of parent to '$str1' no longer suppressed; " +
                    "replaced parent '$str2'"
        })
    }

    fun logGroupPruningSuppressed(
        buildId: Int,
        keepingParent: GroupEntry?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = keepingParent?.logKey
        }, {
            "(Build $long1)     Group pruning suppressed; keeping parent '$str1'"
        })
    }

    fun logPrunedReasonChanged(
        buildId: Int,
        prevReason: String?,
        newReason: String?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = prevReason
            str2 = newReason
        }, {
            "(Build $long1)     Pruned reason changed: $str1 -> $str2"
        })
    }

    fun logFilterChanged(
        buildId: Int,
        prevFilter: NotifFilter?,
        newFilter: NotifFilter?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = prevFilter?.name
            str2 = newFilter?.name
        }, {
            "(Build $long1)     Filter changed: $str1 -> $str2"
        })
    }

    fun logPromoterChanged(
        buildId: Int,
        prevPromoter: NotifPromoter?,
        newPromoter: NotifPromoter?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = prevPromoter?.name
            str2 = newPromoter?.name
        }, {
            "(Build $long1)     Promoter changed: $str1 -> $str2"
        })
    }

    fun logSectionChanged(
        buildId: Int,
        prevSection: NotifSection?,
        newSection: NotifSection?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = prevSection?.label
            str2 = newSection?.label
        }, {
            if (str1 == null) {
                "(Build $long1)     Section assigned: $str2"
            } else {
                "(Build $long1)     Section changed: $str1 -> $str2"
            }
        })
    }

    fun logSectionChangeSuppressed(
        buildId: Int,
        suppressedSection: NotifSection?,
        assignedSection: NotifSection?
    ) {
        buffer.log(TAG, INFO, {
            long1 = buildId.toLong()
            str1 = suppressedSection?.label
            str2 = assignedSection?.label
        }, {
            "(Build $long1)     Suppressing section change to $str1 (staying at $str2)"
        })
    }

    val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()

    fun logFinalList(entries: List<ListEntry>) {
        if (entries.isEmpty()) {
            buffer.log(TAG, DEBUG, {}, { "(empty list)" })
        }
        for (i in entries.indices) {
            val entry = entries[i]
            buffer.log(TAG, DEBUG, {
                int1 = i
                str1 = entry.logKey
                bool1 = logRankInFinalList
                int2 = entry.representativeEntry!!.ranking.rank
            }, {
                "[$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
            })

            if (entry is GroupEntry) {
                entry.summary?.let {
                    buffer.log(TAG, DEBUG, {
                        str1 = it.logKey
                        bool1 = logRankInFinalList
                        int2 = it.ranking.rank
                    }, {
                        "  [*] $str1 (summary)".let { if (bool1) "$it rank=$int2" else it }
                    })
                }
                for (j in entry.children.indices) {
                    val child = entry.children[j]
                    buffer.log(TAG, DEBUG, {
                        int1 = j
                        str1 = child.logKey
                        bool1 = logRankInFinalList
                        int2 = child.ranking.rank
                    }, {
                        "  [$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
                    })
                }
            }
        }
    }

    fun logPipelineRunSuppressed() =
        buffer.log(TAG, INFO, {}, { "Suppressing pipeline run during animation." })
}

private const val TAG = "ShadeListBuilder"
