# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

__all__ = (
    'Deploydb',
    'Host',
    'host_matcher', 'hostname_matcher',
)

import logging; log = logging.getLogger(__name__)
import os, sys, socket, csv
from os import path

from .utils import *
from .objects import XT, fileP, pathP, lowerP, Object, catalog

################################################################################
# Configuration de deploydb

class Deploydb(Object):
    ATTRS = XT(Object, dir=pathP, file=fileP)

################################################################################
# Gestion des groupes

class Group(Object):
    """Groupe d'objets liés.

    Lors du resolve, toutes les variables définies pour le groupe sont propagées
    aux objets liés si elles ne sont pas définies dans ces objets.

    dans l'exemple suivant:
    ~~~
    group mymodules shared=all
        -module module1,module2
            moduledir=~/wop/php
        -host host1,host2
            domain=long.tld
    ~~~
    la variable shared est initialisée pour module1,module2,host1,host2 alors que
    la variable moduledir ne concerne que module1,module2 et la variable domain ne
    concerne que host1,host2
    """

    __RESOLVE_FIRST__ = True

    ATTRS = XT(Object)

    def _resolve(self, catalog):
        for otype, links in self.links.items():
            for link in links:
                object = link.resolve(catalog, resolve=False)
                object.set_defaults(link.attrs)
                object.set_defaults(self.attrs)

################################################################################
# Gestion des hôtes

def withdomain(h): return '.' in h

def fix_host(host, domain=None):
    if host.endswith('.'):
        host = host[:-1]
    elif domain and not withdomain(host):
        host = "%s.%s" % (host, domain)
    return host
def strip_hostname(a):
    pos = a.find('.')
    if pos == -1: return None
    else: return a[pos + 1:]
def strip_domain(a):
    pos = a.find('.')
    if pos == -1: return a
    else: return a[:pos]

def match_host(qhost, object):
    qhost = lowerP.parse(qhost)
    if withdomain(qhost): # host avec chemin
        qhost = fix_host(qhost)
        return qhost in object.get('host', ())
    else: # nom de host
        return qhost in object.get('hostname', ())
def host_matcher(qhost):
    return lambda object: match_host(qhost, object)

def match_hostname(qhost, object):
    qhost = lowerP.parse(qhost)
    qhost = path.basename(qhost)
    return qhost in object.get('hostname', ())
def hostname_matcher(qhost):
    return lambda object: match_hostname(qhost, object)

class Host(Object):
    ATTRS = XT(Object,
               values=lowerP,
               host=lowerP, hostname=lowerP, domain=lowerP, ip=None)

    def _resolve(self, catalog):
        if self.oid == '*': return
        default = catalog.get(self.otype, '*', None, False)

        hosts = self.get('host', [])
        hostnames = self.get('hostname', ())
        domains = self.get('domain', ())

        search_basedir = self.get('search_basedir', ('dirs',))
        files = 'files' in search_basedir
        dirs = 'dirs' in search_basedir
        basedir = self.get('basedir', None)
        if basedir is not None:
            hostdirs = self.resolve_basedir(basedir, files=files, dirs=dirs)
            hosts.extend(map(path.basename, hostdirs))
        dirspec = self.get('dirspec', None)
        if dirspec is not None:
            hostdirs = self.resolve_filespec(dirspec, dirs=True)
            hosts.extend(map(path.basename, hostdirs))
        filespec = self.get('filespec', None)
        if filespec is not None:
            hostfiles = self.resolve_filespec(filespec, files=True)
            hosts.extend(map(path.basename, hostfiles))

        if hosts:
            # générer hostname et domain à partir host
            if not domains:
                domains = set(map(strip_hostname, hosts))
                domains = filter(lambda d: d is not None, domains)
            if not domains and default is not None:
                domains = default.get('domain', ())
            domains = filter(None, domains)
            if domains: domains = self.domain = set(domains)

            hostnames = map(strip_domain, hostnames or hosts)
            if hostnames: hostnames = self.hostname = set(hostnames)

            if domains:
                tmphosts = []
                for host in hosts:
                    for domain in domains:
                        tmphosts.append(fix_host(host, domain))
            else:
                tmphosts = map(fix_host, hosts)
            hosts = self.host = set(tmphosts)

        else:
            # générer host à partir de hostname et domain
            if not domains and default is not None:
                domains = default.get('domain', ())
            if domains: domains = self.domain = set(domains)

            if not hostnames: hostnames = [self.oid]
            hostnames = map(strip_domain, hostnames)
            if hostnames: self.hostname = hostnames

            if domains:
                hosts = []
                for domain in domains:
                    for hostname in hostnames:
                        hosts.append('%s.%s' % (hostname, domain))
            else:
                hosts = hostnames
            if hosts: hosts = self.host = set(hosts)

        ips = self.get('ip', [])
        if not ips:
            for host in hosts:
                try:
                    hostnames, aliases, ipaddrs = socket.gethostbyname_ex(host)
                    ips.extend(ipaddrs)
                except socket.herror, e:
                    log.error("error resolving %s: %s, %s", host, e[0], e[1])
                except socket.gaierror, e:
                    log.error("error resolving %s: %s, %s", host, e[0], e[1])
            if ips: ips = self.ip = set(ips)

        if not self.values:
            self.values = hosts

def save_hosts(*args):
    """Ecrire les hôtes définis sous forme de liste csv, qu'il est possible
    d'exploiter avec 'deploydb loadcsv'

    plus ou moins équivalent à `save_objects host` mais les champs sont dans un
    ordre ergonomique (cette fonction a été écrite en premier, elle est gardée
    pour l'historique)
    """
    # tout d'abord déterminer tous les attributs nécessaires
    headers = ['host', 'hostname', 'domain', 'ip']
    hosts = catalog.find_objects('host')
    for host in hosts:
        for name in host.attrs.keys():
            if name not in headers: headers.append(name)
    # ensuite les écrire
    rows = []
    for host in hosts:
        if host.oid == '*': continue
        row = [host.otype, host.oid]
        for name in headers:
            row.append(';'.join(host.get(name, ())))
        rows.append(row)
    headers[0:0] = ['otype', 'oid']
    # écrire le résultat
    out = csv.writer(sys.stdout)
    out.writerow(headers)
    out.writerows(rows)

################################################################################
# Actions

def save_objects(*args):
    """Ecrire les objets sous forme de liste csv, qu'il est possible d'exploiter
    avec 'deploydb loadcsv'

    usage: save_objects [otype [oids...]]
    """
    otypes = listof(args[0] if args[0:1] else None, None)
    if otypes is not None: otypes = flattenstr(otypes)
    oids = args[1:] or None
    objects = catalog.find_objects(otypes, oids, create=False)
    # tout d'abord déterminer tous les attributs nécessaires
    headers = ['otype', 'oid']
    for object in objects:
        for name in object.known_rw_attrs:
            if name not in headers: headers.append(name)
    for object in objects:
        for name in object.misc_attrs:
            if name not in headers: headers.append(name)
    # ensuite les écrire
    rows = []
    for object in objects:
        row = []
        for name in headers:
            row.append(';'.join(object.get(name, ())))
        rows.append(row)
    # écrire le résultat
    out = csv.writer(sys.stdout)
    out.writerow(headers)
    out.writerows(rows)

def query(*args):
    pass