#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

from winswitch.util.main_loop import test_loop
from winswitch.util.simple_logger import Logger, msig
from winswitch.util.common import csv_list
from winswitch.net.twisted_bonjour import ServiceDescriptor, get_interface_index
from winswitch.net.net_util import if_indextoname
from twisted.internet.defer import Deferred, AlreadyCalledError
from winswitch.net import pybonjour

import sys
DEBUG = "--debug-mdns" in sys.argv

class TwistedBonjourListeners:
	"""
	Aggregates a number of TwistedBonjourListener(s).
	This takes care of constructing the appropriate TwistedBonjourListener with the interface index for the given list of (hosts/interface)s to listen on.
	"""

	def __init__(self, reactor, listen_on, service_type, mdns_found, mdns_add, mdns_remove):
		Logger(self, log_colour=Logger.YELLOW)
		self.listeners = []
		for host in listen_on:
			iface_index = get_interface_index(host)
			self.sdebug("iface_index(%s)=%s" % (host, iface_index), reactor, listen_on, service_type, mdns_found, mdns_add, mdns_remove)
			self.listeners.append(TwistedBonjourListener(reactor, iface_index, service_type, mdns_found, mdns_add, mdns_remove))

	def start(self):
		for listener in self.listeners:
			listener.start()

	def stop(self):
		self.sdebug("listeners=%s" % csv_list(self.listeners))
		for listener in self.listeners:
			try:
				self.sdebug("stopping listener=%s" % listener)
				listener.stop()
			except Exception, e:
				self.serr("error stopping listener %s" % listener, e)
		self.sdebug("all stopped")


class TwistedBonjourListener:
	def __init__(self, reactor, iface_index, service_type, mdns_found=None, mdns_add=None, mdns_remove=None):
		Logger(self, log_colour=Logger.YELLOW)
		self.sdref = None
		self.readers = []
		self.resolvers = []
		self.reactor = reactor
		self.iface_index = iface_index
		self.service_type = service_type
		self.mdns_found = mdns_found
		self.mdns_add = mdns_add
		self.mdns_remove = mdns_remove

	def __str__(self):
		return	"TwistedBonjourListener(%s)" % (self.iface_index)

	def failed(self, errorCode):
		self.serror(None, errorCode)

	def resolving(self, *args):
		self.slog(None, args)

	def	start(self):
		self.slog()
		#d = broadcast(reactor, "_daap._tcp", 3689, "DAAP Server")
		d = self.listen()
		d.addCallback(self.resolving)
		d.addErrback(self.failed)
		return d


	def listen(self):
		#def _callback(*args):
		#	self.sdebug(None, args)
		def resolve_callback(sdref, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord):
			sig = msig(sdref, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord)
			if DEBUG:
				self.debug(sig)
			#handle errors:
			if errorCode!=pybonjour.kDNSServiceErr_NoError:
				try:
					d.errback(errorCode)
				except AlreadyCalledError, e:
					self.serror("%s" % e, sdref, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord)
				return
			#parse results:
			iface = "%s" % interfaceIndex
			if if_indextoname:
				iface = if_indextoname(interfaceIndex)
			e_fullname = fullname.encode().replace("\\032", " ").replace("\\.", ".").replace("\\058",":")
			name = e_fullname.replace(self.service_type, "")
			for x in [".", ".local", ".localdomain"]:
				if name.endswith(x):
					name = name [:(len(name)-len(x))]
			host = ("%s" % hosttarget).encode()
			text_data = {}
			if txtRecord:
				text_data = pybonjour.TXTRecord.parse(txtRecord)
			if DEBUG:
				self.debug(sig+" iface=%s, target=%s:%s, text_data=%s" % (iface, host, port, text_data))
			if self.mdns_add:
				self.mdns_add(interfaceIndex, name, None, host, None, port, text_data)


		def browse_callback(sdref, flags, interfaceIndex, errorCode, name, regtype, domain):
			self.sdebug(None, sdref, flags, interfaceIndex, errorCode, name, regtype, domain)
			#handle errors:
			if errorCode!=pybonjour.kDNSServiceErr_NoError:
				d.errback(errorCode)
				return
			#anything not an "Add", we assume means remove
			if not (flags & pybonjour.kDNSServiceFlagsAdd):
				if self.mdns_remove:
					self.mdns_remove(name)
				return
			#this is an 'Add'
			if self.mdns_found:
				self.mdns_found(interfaceIndex, name)

			#now resolve it:
			resolve = pybonjour.DNSServiceResolve(0, interfaceIndex, name,
									regtype, domain, resolve_callback)
			self.resolvers.append(resolve)
			reader = ServiceDescriptor(resolve)
			self.reactor.addReader(reader)
			self.readers.append(reader)

		d = Deferred()
		self.sdebug("listening for service %s on interface %s" % (self.service_type, self.iface_index))
		try:
			self.sdref = pybonjour.DNSServiceBrowse(regtype=self.service_type, interfaceIndex=self.iface_index, callBack=browse_callback)
		except pybonjour.BonjourError, e:
			if e.errorCode==-65537:
				self.serror("mDNS service is not running: %s, failed to register service %s on interface %s" % (e, self.service_type, self.iface_index))
			else:
				self.serr(None, e)
			raise e
		reader = ServiceDescriptor(self.sdref)
		self.reactor.addReader(reader)
		self.readers.append(reader)
		return d

	def stop(self):
		self.sdebug("readers=%s, resolvers=%s, sdref=%s" % (csv_list(self.readers), self.resolvers, self.sdref))
		for r in self.readers:
			try:
				self.sdebug("removing reader=%s" % r)
				self.reactor.removeReader(r)
			except Exception, e:
				self.serr("removing %s" % r, e)
		for r in self.resolvers:
			try:
				self.sdebug("removing resolver=%s" % r)
				r.close()
			except Exception, e:
				self.serr("error closing %s" % r, e)
		if self.sdref:
			self.sdebug("closing sdref=%s" % self.sdref)
			self.sdref.close()
		else:
			self.serr("not started or already stopped!")
		self.sdebug("all closed")


def main():
	def mdns_found(*args):
		print("mdns_found")
	def mdns_add(*args):
		print("mdns_add")
	def mdns_remove(*args):
		print("mdns_remove")
	from twisted.internet import reactor
	from winswitch.consts import MDNS_TYPE
	host = ""		#any
	listener = TwistedBonjourListeners(reactor, [host], MDNS_TYPE, mdns_found, mdns_add, mdns_remove)
	test_loop(listener.start)


if __name__ == "__main__":
	main()
