""" DNS Resolver

This module contains a class for resolving hostnames. You will have to
implement things in this module. This resolver will be both used by the DNS
client and the DNS server, but with a different list of servers.
"""

import re
import socket

from dns.cache import RecordCache
from dns.classes import Class
from dns.message import Message, Header, Question
from dns.types import Type
import dns.regexes as rgx


class Resolver(object):
    """ DNS resolver """

    ROOT_SERVERS = [
        '198.41.0.4',
        '192.228.79.201',
        '192.33.4.12',
        '199.7.91.13',
        '192.203.230.10',
        '192.5.5.241',
        '192.112.36.4',
        '198.97.190.53',
        '192.36.148.17',
        '192.58.128.30',
        '193.0.14.129',
        '199.7.83.42',
        '202.12.27.33'
        ]

    def __init__(self, nameservers, timeout, caching, ttl):
        """ Initialize the resolver

        Args:
            caching (bool): caching is enabled if True
            ttl (int): ttl of cache entries (if > 0)
        """
        self.nameservers = nameservers + self.ROOT_SERVERS
        self.timeout = timeout
        self.caching = caching
        self.ttl = ttl

        if self.caching:
            self.cache = RecordCache()

    def do_query(self, query, using):
        """Send a query to a list of name servers"""
        for hint in using:
            if re.match(rgx.IP, hint) is None:
                continue
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.settimeout(self.timeout)
            try:
                sock.sendto(query.to_bytes(), (hint, 53))
                data = sock.recv(512)
                response = Message.from_bytes(data)

                if self.caching:
                    for record in response.answers + \
                            response.authorities + \
                            response.additionals:
                        self.cache.add_record(record)

                yield response
            except socket.timeout:
                pass

    def try_hint(self, sub, dom, hints):
        """Helper for get_hints"""
        if sub == '':
            for hint in hints:
                yield hint
        else:
            for new_hint in self.get_hints(
                    sub, dom, hints, in_recursion=True):
                yield new_hint

    def get_hints(self, domain, parent='', using=None, in_recursion=False):
        """Get a list of nameservers for a domain"""
        if using is None:
            using = []

        if not in_recursion:
            using += self.nameservers

        print 'Trying', domain, parent, 'using', using

        domains = re.match(rgx.DOMAIN, domain)
        if domains is None:
            return
        sub, dom = domains.groups()
        if parent != '':
            dom += '.' + parent

        if self.caching:
            hints = self.cache.lookup(dom, Type.NS, Class.IN)
            for hint in self.try_hint(
                    sub, dom, [r.rdata.data for r in hints]):
                yield hint

        header = Header(0, 0, 1, 0, 0, 0)
        header.qr = 0
        header.opcode = 0
        header.rd = 0
        query = Message(header, [Question(dom, Type.NS, Class.IN)])

        for response in self.do_query(query, using):
            hints = [ip for _, [ip] in response.get_hints()]
            for hint in self.try_hint(sub, dom, hints):
                yield hint

    def gethostbyname(self, hostname):
        """ Translate a host name to IPv4 address.

        Args:
            hostname (str): the hostname to resolve

        Returns:
            (str, [str], [str]): (hostname, aliaslist, ipaddrlist)
        """
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(self.timeout)

        if self.caching:
            addrs = list(self.cache.lookup(hostname, Type.A, Class.IN))
            cnames = list(self.cache.lookup(hostname, Type.CNAME, Class.IN))
            if addrs != []:
                return (
                    hostname,
                    [r.rdata.data for r in cnames],
                    [r.rdata.data for r in addrs])

        # Create and send query
        question = Question(hostname, Type.A, Class.IN)
        header = Header(9001, 0, 1, 0, 0, 0)
        header.qr = 0
        header.opcode = 0
        header.rd = 1
        query = Message(header, [question])

        for response in self.do_query(query, self.get_hints(hostname)):
            aliases = response.get_aliases()
            addresses = response.get_addresses()

            return hostname, aliases, addresses

        return hostname, [], []