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

import android.util.ArrayMap
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject

/**
 * Maintains a registry of things that should be dumped when a bug report is taken
 *
 * When a bug report is taken, SystemUI dumps various diagnostic information that we hope will be
 * useful for the eventual readers of the bug report. Code that wishes to participate in this dump
 * should register itself here.
 *
 * See [DumpHandler] for more information on how and when this information is dumped.
 */
@SysUISingleton
class DumpManager @Inject constructor() {
    private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
    private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()

    /**
     * Register a dumpable to be called during a bug report. The dumpable will be called during the
     * CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.
     *
     * @param name The name to register the dumpable under. This is typically the qualified class
     * name of the thing being dumped (getClass().getName()), but can be anything as long as it
     * doesn't clash with an existing registration.
     */
    @Synchronized
    fun registerDumpable(name: String, module: Dumpable) {
        if (!canAssignToNameLocked(name, module)) {
            throw IllegalArgumentException("'$name' is already registered")
        }

        dumpables[name] = RegisteredDumpable(name, module)
    }

    /**
     * Unregisters a previously-registered dumpable.
     */
    @Synchronized
    fun unregisterDumpable(name: String) {
        dumpables.remove(name)
    }

    /**
     * Register a [LogBuffer] to be dumped during a bug report.
     */
    @Synchronized
    fun registerBuffer(name: String, buffer: LogBuffer) {
        if (!canAssignToNameLocked(name, buffer)) {
            throw IllegalArgumentException("'$name' is already registered")
        }
        buffers[name] = RegisteredDumpable(name, buffer)
    }

    /**
     * Dumps the first dumpable or buffer whose registered name ends with [target]
     */
    @Synchronized
    fun dumpTarget(
        target: String,
        fd: FileDescriptor,
        pw: PrintWriter,
        args: Array<String>,
        tailLength: Int
    ) {
        for (dumpable in dumpables.values) {
            if (dumpable.name.endsWith(target)) {
                dumpDumpable(dumpable, fd, pw, args)
                return
            }
        }

        for (buffer in buffers.values) {
            if (buffer.name.endsWith(target)) {
                dumpBuffer(buffer, pw, tailLength)
                return
            }
        }
    }

    /**
     * Dumps all registered dumpables to [pw]
     */
    @Synchronized
    fun dumpDumpables(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
        for (module in dumpables.values) {
            dumpDumpable(module, fd, pw, args)
        }
    }

    /**
     * Dumps the names of all registered dumpables (one per line)
     */
    @Synchronized
    fun listDumpables(pw: PrintWriter) {
        for (module in dumpables.values) {
            pw.println(module.name)
        }
    }

    /**
     * Dumps all registered [LogBuffer]s to [pw]
     */
    @Synchronized
    fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
        for (buffer in buffers.values) {
            dumpBuffer(buffer, pw, tailLength)
        }
    }

    /**
     * Dumps the names of all registered buffers (one per line)
     */
    @Synchronized
    fun listBuffers(pw: PrintWriter) {
        for (buffer in buffers.values) {
            pw.println(buffer.name)
        }
    }

    @Synchronized
    fun freezeBuffers() {
        for (buffer in buffers.values) {
            buffer.dumpable.freeze()
        }
    }

    @Synchronized
    fun unfreezeBuffers() {
        for (buffer in buffers.values) {
            buffer.dumpable.unfreeze()
        }
    }

    private fun dumpDumpable(
        dumpable: RegisteredDumpable<Dumpable>,
        fd: FileDescriptor,
        pw: PrintWriter,
        args: Array<String>
    ) {
        pw.println()
        pw.println("${dumpable.name}:")
        pw.println("----------------------------------------------------------------------------")
        dumpable.dumpable.dump(fd, pw, args)
    }

    private fun dumpBuffer(
        buffer: RegisteredDumpable<LogBuffer>,
        pw: PrintWriter,
        tailLength: Int
    ) {
        pw.println()
        pw.println()
        pw.println("BUFFER ${buffer.name}:")
        pw.println("============================================================================")
        buffer.dumpable.dump(pw, tailLength)
    }

    private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
        val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
        return existingDumpable == null || newDumpable == existingDumpable
    }
}

private data class RegisteredDumpable<T>(
    val name: String,
    val dumpable: T
)

private const val TAG = "DumpManager"