package com.ccnet.acc

import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import timber.log.Timber
import java.io.FileDescriptor
import java.io.IOException
import java.lang.reflect.Field

object NetworkAdapter {
    val JNI_CLAZZ_NAME = NetworkAdapter::class.java.name.replace(".", "/")

    var wifiNetwork: Network? = null
    var cellularNetwork: Network? = null
    var bindNetwork: Network? = null

    private val FD_FIELD: Field

    val wifiRequest: NetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()

    val cellularRequest: NetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()

    fun initNetworks(context: Context) {
        connectivityManager(context).requestNetwork(wifiRequest, object : NetworkCallback() {
            override fun onAvailable(network: Network) {
                wifiNetwork = network
            }
            override fun onLost(network: Network) {
                wifiNetwork = null
            }
        })
        connectivityManager(context).requestNetwork(cellularRequest, object : NetworkCallback() {
            override fun onAvailable(network: Network) {
                cellularNetwork = network
            }
            override fun onLost(network: Network) {
                cellularNetwork = null
            }
        })
    }

    fun useWifi(context: Context) {
        connectivityManager(context).requestNetwork(wifiRequest, object : NetworkCallback() {
            override fun onAvailable(network: Network) {
                bindNetwork = network
            }

            override fun onLost(network: Network) {
                bindNetwork = null
            }
        })
    }

    fun useCellular(context: Context) {
        connectivityManager(context).requestNetwork(cellularRequest, object : NetworkCallback() {
            override fun onAvailable(network: Network) {
                bindNetwork = network
            }

            override fun onLost(network: Network) {
                bindNetwork = null
            }
        })
    }

    fun useSystem() {
        bindNetwork = null
    }

    private fun connectivityManager(context: Context): ConnectivityManager {
        return context.getSystemService(ConnectivityManager::class.java)
    }

    @JvmStatic
    fun bindSocket(rawFd: Int) {
        val bindNetwork = bindNetwork ?: return
        val fd = FileDescriptor()
        try {
            FD_FIELD[fd] = rawFd
            NetworkAdapter.bindNetwork!!.bindSocket(fd)
            Timber.d("bind socket success")
        } catch (e: IllegalAccessException) {
            // ignore
            Timber.e("bind socket failed $e")
        } catch (e: IOException) {
            Timber.e("bind socket failed $e")
        }
    }

    init {
        try {
            @SuppressLint("DiscouragedPrivateApi")
            FD_FIELD = FileDescriptor::class.java.getDeclaredField("descriptor")
        } catch (e: NoSuchFieldException) {
            throw RuntimeException(e)
        }
        FD_FIELD.isAccessible = true
    }
}