package tech.platypush.app

import android.content.ContentValues.TAG
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.util.Log
import java.util.*
import java.util.concurrent.locks.ReentrantLock


/**
 * Service data class
 */
data class Service(val host: String, val port: Int, val name: String?) {
    fun toMap(): Map<String, Any?> {
       return mapOf(
           "host" to host,
           "port" to port,
           "name" to name
       )
    }
}


/**
 * ZeroConf/Bonjour/mDNS event listener
 */
class Listener(private val scanner: Scanner): NsdManager.DiscoveryListener {
    val serviceType = "_platypush-http._tcp."

    override fun onDiscoveryStarted(regType: String) {
        Log.d(TAG, "Service discovery started")
    }

    override fun onServiceFound(service: NsdServiceInfo) {
        Log.d(TAG, "Service discovery succeeded: $service")
        if (service.serviceType != serviceType) {
            Log.d(TAG, "Unknown service type: ${service.serviceType}")
            return
        }

        scanner.resolveService(service)
    }

    override fun onServiceLost(service: NsdServiceInfo) {
        Log.w(TAG, "Service lost: $service")
    }

    override fun onDiscoveryStopped(serviceType: String) {
        Log.i(TAG, "Discovery stopped: $serviceType")
    }

    override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
        Log.e(TAG, "Discovery start failed: Error code: $errorCode")
        scanner.stopScan()
    }

    override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
        Log.e(TAG, "Discovery stop failed: Error code: $errorCode")
        scanner.stopScan()
    }
}


/**
 * ZeroConf/Bonjour/mDNS service scanner and resolver
 */
class Scanner(context: Context) {
    private val nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
    private val listener = Listener(this)
    private val services = HashMap<Pair<String, Int>, Service>()
    private val resolverLock = ReentrantLock()

    fun startScan() {
        nsdManager.discoverServices(listener.serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
    }

    fun stopScan() {
        nsdManager.stopServiceDiscovery(listener)
    }

    fun getServices(): Collection<Service> {
        return services.values
    }

    fun resolveService(service: NsdServiceInfo) {
        // Service resolution is a critical section
        resolverLock.lock()
        val scanner = this

        nsdManager.resolveService(service, object : NsdManager.ResolveListener {
            override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
                resolverLock.unlock()
                val msg = "Resolve of service ${serviceInfo.serviceName} failed"

                // Retry logic
                when (errorCode) {
                    NsdManager.FAILURE_ALREADY_ACTIVE -> {
                        Thread.sleep(100)
                        scanner.resolveService(serviceInfo)
                        Log.w(TAG,  "$msg: Resolver already active")
                    }

                    NsdManager.FAILURE_MAX_LIMIT -> {
                        Thread.sleep(5000)
                        scanner.resolveService(serviceInfo)
                        Log.e(TAG,  "$msg: Maximum number of resolve requests reached")
                    }

                    NsdManager.FAILURE_INTERNAL_ERROR -> {
                        Log.e(TAG,  "$msg: Internal error")
                    }
                }
            }

            override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
                services[Pair(serviceInfo.host.hostAddress, serviceInfo.port)] = Service(
                    serviceInfo.host.hostAddress, serviceInfo.port, serviceInfo.serviceName)
                resolverLock.unlock()
                Log.i(TAG, "Resolve succeeded: $serviceInfo")
            }
        })
    }
}
