class WifiConnectionViewModel(
    private val context: Context,
    wifiScanUseCase: WiFiScanUseCase,
    private val wifiConnectUseCase: WiFiConnectDockUseCase,
    private val wifiConnectLegacyUseCase: WiFiConnectDockLegacyUseCase,
    private val wifiDisconnectUseCase: WiFiDisconnectDockUseCase
) : ViewModel() {

    private val disposables = CompositeDisposable();

    private var networkCallback: ConnectivityManager.NetworkCallback? = null

    init {
        disposables.add(
            findDockUseCase()
                .andThen(connect())
                .doOnSuccess { lockNetwork(it) }
                // here, once connected, we usually communicate with the device
                .doOnSuccess { unlockNetwork() }
                .doOnTerminate { disconnectDockUseCase(networkCallback) }
                .doOnTerminate { networkCallback = null }
                .subscribe({
                    Timber.v("Successfully finished test.")
                }, {
                    Timber.v("Test failed: ${it.message}")
                })
        )
    }

    private fun connect(): Single<Network> {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            connectDockUseCase()
                .doOnSuccess { networkCallback = it.second }
                .map { it.first }
        } else {
            connectDockLegacyUseCase()
        }
    }
        
    private fun lockNetwork(it: Network) {
        Timber.v("Binding process to network")
        context.bindProcessToNetwork(it)
    }

    private fun unlockNetwork() {
        Timber.v("Unbinding process to network")
        context.unbindProcessToNetwork()
    }

    override fun onCleared() {
        super.onCleared()
        disposables.clear()
    }
}

/**
 * If the network doesn't have internet connectivity network, requests will not be routed to it.
 * To direct all the network requests from your app to an external Wi-Fi device bind a network using
 * this function and all traffic will be routed to this network even if it has not internet connection.
 *
 * Call Context#unbindProcessToNetwork an extension function in order to unbind.
 */
fun Context.bindProcessToNetwork(network: Network) {
    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        connectivityManager.bindProcessToNetwork(network)
    } else {
        ConnectivityManager.setProcessDefaultNetwork(network)
    }
}

/**
 * To unbind direction of all network requests and let the system decide which the traffic channel
 * will use if there is no internet connection.
 */
fun Context.unbindProcessToNetwork() {
    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        connectivityManager.bindProcessToNetwork(null)
    } else {
        ConnectivityManager.setProcessDefaultNetwork(null)
    }
}