import time
import logging

from zeroconf import ServiceBrowser, Zeroconf, IPVersion
from pymoku import MokuInfo

log = logging.getLogger(__name__)


class Finder(object):
    def __init__(self, ipv6=False, on_add=None, on_remove=None):
        self.moku_list = []
        self.finished = False
        self.timeout = 5
        self.filter = None
        self.on_add = on_add
        self.on_remove = on_remove
        self.ip_ver = IPVersion.V6Only if ipv6 else IPVersion.V4Only

    def _parse_05(self, info):
        name = info.name.split('.' + info.type)[0]
        ip_addr = info.parsed_addresses(self.ip_ver)[0]
        p = info.properties
        minfo = MokuInfo(name=name,
                         netver=int(p[b'netver']),
                         fwver=int(p[b'fwver']),
                         hwver=float(p[b'hwver']),
                         serial=int(p[b'serial']),
                         colour=(p.get(b'colour', p.get(b'color')) or b'').decode('utf8'),
                         bootmode=p[b'bootmode'].decode('utf8'),
                         ip_addr=ip_addr)
        return minfo

    def _parse_04(self, info):
        name = info.name.split('.' + info.type)[0]
        ip_addr = info.parsed_addresses(self.ip_ver)[0]
        p = info.properties
        minfo = MokuInfo(name=name,
                         netver=int(p[b'netver']),
                         fwver=int(p[b'device.fw_version']),
                         hwver=float(p[b'device.hw_version']),
                         serial=int(p[b'device.serial']),
                         colour=p[b'device.colour'].decode('utf8'),
                         bootmode=p[b'system.bootmode'].decode('utf8'),
                         ip_addr=ip_addr)
        return minfo

    def _parse_02(self, info):
        name = info.name.split('.' + info.type)[0]
        ip_addr = info.parsed_addresses(self.ip_ver)[0]
        p = info.properties
        minfo = MokuInfo(name=name,
                         netver=int(p[b'netver']),
                         fwver=int(p[b'system.micro']),
                         hwver=float(p[b'device.hw_version']),
                         serial=int(p[b'device.serial']),
                         colour=p[b'device.colour'].decode('utf8'),
                         bootmode=p[b'system.bootmode'].decode('utf8'),
                         ip_addr=ip_addr)
        return minfo

    def add_service(self, zeroconf, service_type, name):
        info = zeroconf.get_service_info(service_type, name)

        if info is None:
            log.warning(f"Couldn't resolve {name}")
            return

        for _ in range(3):
            if info.addresses:
                break
            info = zeroconf.get_service_info(service_type, name)
        else:
            log.warning(f"Couldn't resolve an address for {name}")
            return

        try:
            minfo = {0.2: self._parse_02,
                     0.4: self._parse_04,
                     0.5: self._parse_05}.get(float(info.properties[b'txtver']), self._parse_05)(info)
        except Exception:
            log.warning(f"Couldn't parse txt-record for {name}")
            return

        if self.filter is None or self.filter(minfo):
            self.moku_list.append(minfo)

            if self.on_add:
                self.on_add(name, minfo)

    def remove_service(self, zeroconf, service_type, name):
        if self.on_remove:
            self.on_remove(name)

    def update_service(self, zeroconf, service_type, name):
        self.add_service(zeroconf, service_type, name)

    def start(self):
        self.zero_conf = Zeroconf(ip_version=self.ip_ver)
        self.browser = ServiceBrowser(self.zero_conf, "_moku._tcp.local.",
                                      listener=self)

    def close(self):
        self.zero_conf.close()

    def find_all(self, timeout=5, filter=None):
        self.timeout = timeout
        self.filter = filter
        self.start()
        start = time.time()
        try:
            while time.time() - start < timeout and not self.browser.done and (
                    not self.finished):
                time.sleep(0.1)
        except KeyboardInterrupt:
            pass
        finally:
            self.zero_conf.close()
        return self.moku_list
