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

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.ArraySet
import com.android.systemui.Dumpable
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
import com.android.systemui.util.indentIfPossible
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger

/**
 * Receiver for a given action-userId pair to be used by [UserBroadcastDispatcher].
 *
 * Each object of this class will take care of a single Action. It will register if it has at least
 * one [BroadcastReceiver] added to it, and unregister when none are left.
 *
 * It will also re-register if filters with new categories are added. But this should not happen
 * often.
 *
 * This class has no sync controls, so make sure to only make modifications from the background
 * thread.
 */
class ActionReceiver(
    private val action: String,
    private val userId: Int,
    private val registerAction: BroadcastReceiver.(IntentFilter) -> Unit,
    private val unregisterAction: BroadcastReceiver.() -> Unit,
    private val bgExecutor: Executor,
    private val logger: BroadcastDispatcherLogger
) : BroadcastReceiver(), Dumpable {

    companion object {
        val index = AtomicInteger(0)
    }

    var registered = false
        private set
    private val receiverDatas = ArraySet<ReceiverData>()
    private val activeCategories = ArraySet<String>()

    @Throws(IllegalArgumentException::class)
    fun addReceiverData(receiverData: ReceiverData) {
        if (!receiverData.filter.hasAction(action)) {
            throw(IllegalArgumentException("Trying to attach to $action without correct action," +
                "receiver: ${receiverData.receiver}"))
        }
        val addedCategories = activeCategories
                .addAll(receiverData.filter.categoriesIterator()?.asSequence() ?: emptySequence())

        if (receiverDatas.add(receiverData) && receiverDatas.size == 1) {
            registerAction(createFilter())
            registered = true
        } else if (addedCategories) {
            unregisterAction()
            registerAction(createFilter())
        }
    }

    fun hasReceiver(receiver: BroadcastReceiver): Boolean {
        return receiverDatas.any { it.receiver == receiver }
    }

    private fun createFilter(): IntentFilter {
        val filter = IntentFilter(action)
        activeCategories.forEach(filter::addCategory)
        return filter
    }

    fun removeReceiver(receiver: BroadcastReceiver) {
        if (receiverDatas.removeAll { it.receiver == receiver } &&
                receiverDatas.isEmpty() && registered) {
            unregisterAction()
            registered = false
            activeCategories.clear()
        }
    }

    @Throws(IllegalStateException::class)
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action != action) {
            throw(IllegalStateException("Received intent for ${intent.action} " +
                "in receiver for $action}"))
        }
        val id = index.getAndIncrement()
        logger.logBroadcastReceived(id, userId, intent)
        // Immediately return control to ActivityManager
        bgExecutor.execute {
            receiverDatas.forEach {
                if (it.filter.matchCategories(intent.categories) == null) {
                    it.executor.execute {
                        it.receiver.pendingResult = pendingResult
                        it.receiver.onReceive(context, intent)
                        logger.logBroadcastDispatched(id, action, it.receiver)
                    }
                }
            }
        }
    }

    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
        pw.indentIfPossible {
            println("Registered: $registered")
            println("Receivers:")
            pw.indentIfPossible {
                receiverDatas.forEach {
                    println(it.receiver)
                }
            }
            println("Categories: ${activeCategories.joinToString(", ")}")
        }
    }
}