"""Discovery application."""

import time
import uuid

from .actions import *
from .uri import URI
from .util import matchesFilter, filterServices, extractSoapUdpAddressFromURI
from .service import Service
from .namespaces import NS_DISCOVERY
from .threaded import ThreadedNetworking
from .daemon import Daemon

DEFAULT_DISCOVERY_TIMEOUT = 3


class Discovery:
    """networking-agnostic generic remote service discovery mixin"""

    def __init__(self, **kwargs):
        self._remoteServices = {}
        self._remoteServiceHelloCallback = None
        self._remoteServiceHelloCallbackTypesFilter = None
        self._remoteServiceHelloCallbackScopesFilter = None
        self._remoteServiceByeCallback = None
        super().__init__(**kwargs)

    def setRemoteServiceHelloCallback(self, cb, types=None, scopes=None):
        """Set callback, which will be called when new service appeared online
        and sent Hi message

        typesFilter and scopesFilter might be list of types and scopes.
        If filter is set, callback is called only for Hello messages,
        which match filter

        Set None to disable callback
        """
        self._remoteServiceHelloCallback = cb
        self._remoteServiceHelloCallbackTypesFilter = types
        self._remoteServiceHelloCallbackScopesFilter = scopes

    def setRemoteServiceByeCallback(self, cb):
        """Set callback, which will be called when new service appeared online
        and sent Hi message
        Service is passed as a parameter to the callback
        Set None to disable callback
        """
        self._remoteServiceByeCallback = cb

    def setRemoteServiceDisappearedCallback(self, cb):
        """Set callback, which will be called when new service disappears
        Service uuid is passed as a parameter to the callback
        Set None to disable callback
        """
        self._remoteServiceDisppearedCallback = cb

    # discovery-related message handlers:

    def _handle_probematches(self, env, addr):
        for match in env.getProbeResolveMatches():
            self._addRemoteService(Service(match.getTypes(), match.getScopes(), match.getXAddrs(), match.getEPR(), 0))
            if match.getXAddrs() is None or len(match.getXAddrs()) == 0:
                self._sendResolve(match.getEPR())

    def _handle_resolvematches(self, env, addr):
        for match in env.getProbeResolveMatches():
            self._addRemoteService(Service(match.getTypes(), match.getScopes(), match.getXAddrs(), match.getEPR(), 0))

    def _handle_hello(self, env, addr):
        #check if it is from a discovery proxy
        rt = env.getRelationshipType()
        if rt is not None and rt.getLocalname() == "Suppression" and rt.getNamespace() == NS_DISCOVERY:
            xAddr = env.getXAddrs()[0]
            #only support 'soap.udp'
            if xAddr.startswith("soap.udp:"):
                self._dpActive = True
                self._dpAddr = extractSoapUdpAddressFromURI(URI(xAddr))
                self._dpEPR = env.getEPR()

        service = Service(env.getTypes(), env.getScopes(), env.getXAddrs(), env.getEPR(), 0)
        self._addRemoteService(service)
        if self._remoteServiceHelloCallback is not None:
            if matchesFilter(service,
                             self._remoteServiceHelloCallbackTypesFilter,
                             self._remoteServiceHelloCallbackScopesFilter):
                self._remoteServiceHelloCallback(service)

    def _handle_bye(self, env, addr):
        #if the bye is from discovery proxy... revert back to multicasting
        if self._dpActive and self._dpEPR == env.getEPR():
            self._dpActive = False
            self._dpAddr = None
            self._dpEPR = None

        self._removeRemoteService(env.getEPR())
        if self._remoteServiceByeCallback is not None:
            self._remoteServiceByeCallback(env.getEPR())


    # handle local address changes:

    def  _networkAddressAdded(self, addr):
        self.addSourceAddr(addr)

    def _networkAddressRemoved(self, addr):
        self.removeSourceAddr(addr)

    # search for & keep track of discovered remote services:

    def _addRemoteService(self, service):
        self._remoteServices[service.getEPR()] = service

    def _removeRemoteService(self, epr):
        if epr in self._remoteServices:
            del self._remoteServices[epr]

    def clearRemoteServices(self):
        'clears remotely discovered services'

        self._remoteServices.clear()

    def searchServices(self, types=None, scopes=None, address=None, port=None,
                       timeout=DEFAULT_DISCOVERY_TIMEOUT):
        'search for services given the TYPES and SCOPES within a given TIMEOUT'
        try:
            self._sendProbe(types, scopes, address, port)
        except:
            raise Exception("Server not started")

        time.sleep(timeout)

        return filterServices(list(self._remoteServices.values()), types, scopes)

    def stop(self):
        self.clearRemoteServices()
        super().stop()


class ThreadedWSDiscovery(Daemon, Discovery, ThreadedNetworking):
    "Full threaded service discovery implementation"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def stop(self):
        super().stop()

