/*
 * Copyright (C) 2022 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.pipeline.wifi.data.repository

import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn

/**
 * Provides the [WifiRepository] interface either through the [DemoWifiRepository] implementation,
 * or the [WifiRepositoryImpl]'s prod implementation, based on the current demo mode value. In this
 * way, downstream clients can all consist of real implementations and not care about which
 * repository is responsible for the data. Graphically:
 * ```
 * RealRepository
 *                 │
 *                 ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
 *                 │
 * DemoRepository
 * ```
 *
 * When demo mode turns on, every flow will [flatMapLatest] to the current provider's version of
 * that flow.
 */
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class WifiRepositorySwitcher
@Inject
constructor(
    private val realImpl: RealWifiRepository,
    private val demoImpl: DemoWifiRepository,
    private val demoModeController: DemoModeController,
    @Application scope: CoroutineScope,
) : WifiRepository {
    private val isDemoMode =
        conflatedCallbackFlow {
                val callback =
                    object : DemoMode {
                        override fun dispatchDemoCommand(command: String?, args: Bundle?) {
                            // Don't care
                        }

                        override fun onDemoModeStarted() {
                            demoImpl.startProcessingCommands()
                            trySend(true)
                        }

                        override fun onDemoModeFinished() {
                            demoImpl.stopProcessingCommands()
                            trySend(false)
                        }
                    }

                demoModeController.addCallback(callback)
                awaitClose { demoModeController.removeCallback(callback) }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)

    @VisibleForTesting
    val activeRepo =
        isDemoMode
            .mapLatest { isDemoMode ->
                if (isDemoMode) {
                    demoImpl
                } else {
                    realImpl
                }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)

    override val isWifiEnabled: StateFlow<Boolean> =
        activeRepo
            .flatMapLatest { it.isWifiEnabled }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiEnabled.value)

    override val isWifiDefault: StateFlow<Boolean> =
        activeRepo
            .flatMapLatest { it.isWifiDefault }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiDefault.value)

    override val wifiNetwork: StateFlow<WifiNetworkModel> =
        activeRepo
            .flatMapLatest { it.wifiNetwork }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)

    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
        activeRepo
            .flatMapLatest { it.secondaryNetworks }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.secondaryNetworks.value)

    override val wifiActivity: StateFlow<DataActivityModel> =
        activeRepo
            .flatMapLatest { it.wifiActivity }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)

    override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
        activeRepo
            .flatMapLatest { it.wifiScanResults }
            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiScanResults.value)
}
