# Copyright (c) 2017, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.

'''Peer management.'''

import asyncio
import socket
from collections import namedtuple

import lib.util as util
from server.irc import IRC


IRCPeer = namedtuple('IRCPeer', 'ip_addr host details')


class PeerManager(util.LoggedClass):
    '''Looks after the DB of peer network servers.

    Attempts to maintain a connection with up to 8 peers.
    Issues a 'peers.subscribe' RPC to them and tells them our data.
    '''
    PROTOCOL_VERSION = '1.0'

    def __init__(self, env, controller):
        super().__init__()
        self.env = env
        self.controller = controller
        self.irc = IRC(env, self)
        self.pruning = None
        self._identities = []
        # Keyed by nick
        self.irc_peers = {}
        self._identities.append(env.identity)
        if env.tor_identity.host.endswith('.onion'):
            self._identities.append(env.tor_identity)

    def real_name(self, host, protocol_version, tcp_port, ssl_port):
        '''Real name as used on IRC.'''
        default_ports = self.env.coin.PEER_DEFAULT_PORTS

        def port_text(letter, port):
            if port == default_ports.get(letter):
                return letter
            else:
                return letter + str(port)

        parts = [host, 'v' + protocol_version]
        for letter, port in (('s', ssl_port), ('t', tcp_port)):
            if port:
                parts.append(port_text(letter, port))
        return ' '.join(parts)

    def irc_name_pairs(self):
        return [(self.real_name(identity.host, self.PROTOCOL_VERSION,
                                identity.tcp_port, identity.ssl_port),
                 identity.nick_suffix)
                for identity in self._identities]

    def identities(self):
        '''Return a list of network identities of this server.'''
        return self._identities

    def ensure_future(self, coro, callback=None):
        '''Schedule the coro to be run.'''
        return self.controller.ensure_future(coro, callback=callback)

    async def main_loop(self):
        '''Not a loop for now...'''
        if self.env.irc:
            self.ensure_future(self.irc.start(self.irc_name_pairs()))
        else:
            self.logger.info('IRC is disabled')

    def dns_lookup_peer(self, nick, hostname, details):
        try:
            ip_addr = None
            try:
                ip_addr = socket.gethostbyname(hostname)
            except socket.error:
                pass  # IPv6?
            ip_addr = ip_addr or hostname
            self.irc_peers[nick] = IRCPeer(ip_addr, hostname, details)
            self.logger.info('new IRC peer {} at {} ({})'
                             .format(nick, hostname, details))
        except UnicodeError:
            # UnicodeError comes from invalid domains (issue #68)
            self.logger.info('IRC peer domain {} invalid'.format(hostname))

    def add_irc_peer(self, *args):
        '''Schedule DNS lookup of peer.'''
        self.controller.schedule_executor(self.dns_lookup_peer, *args)

    def remove_irc_peer(self, nick):
        '''Remove a peer from our IRC peers map.'''
        self.logger.info('removing IRC peer {}'.format(nick))
        self.irc_peers.pop(nick, None)

    def count(self):
        return len(self.irc_peers)

    def rpc_data(self):
        return self.irc_peers

    def on_peers_subscribe(self):
        '''Returns the server peers as a list of (ip, host, details) tuples.'''
        return list(self.irc_peers.values())