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

__all__ = (
    'Module',
    'module_matcher', 'modulename_matcher',
)

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

from .utils import *
from .expr import *
from .objects import XT, fileP, pathP, mpathP, lowerP, Object, catalog
from .parser import Parser
from .base_module import withdomain, host_matcher, hostname_matcher

################################################################################
# Module

def match_module(qmodule, object):
    if withpath(qmodule): # module avec chemin
        return qmodule in object.get('module', ())
    else: # nom de module
        return qmodule in object.get('modulename', ())
def module_matcher(qmodule):
    return lambda object: match_module(qmodule, object)

def match_modulename(qmodule, object):
    qmodule = path.basename(qmodule)
    return qmodule in object.get('modulename', ())
def modulename_matcher(qmodule):
    return lambda object: match_modulename(qmodule, object)

class Module(Object):
    ATTRS = XT(Object,
               values=pathP, module=mpathP, moduledir=pathP,
               basedir=pathP, dirspec=fileP)

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

        modules = self.get('module', [])
        basedir = self.get('basedir', None)
        if basedir is not None:
            modules.extend(self.resolve_basedir(basedir, dirs=True))
        dirspec = self.get('dirspec', None)
        if dirspec is not None:
            modules.extend(self.resolve_filespec(dirspec, dirs=True))

        if modules:
            # générer moduledir et modulename à partir de module
            moduledirs = [path.dirname(module) for module in modules]
            if moduledirs: moduledirs = self.moduledir = ulistof(moduledirs)

            modulenames = [path.basename(module) for module in modules]
            if modulenames: modulenames = self.modulename = ulistof(modulenames)

        else:
            # générer modules à partir de moduledir et modulename
            moduledirs = self.get('moduledir', ())
            if not moduledirs and default is not None:
                moduledirs = default.get('moduledir', ())
            if moduledirs: moduledirs = self.moduledir = ulistof(moduledirs)

            modulenames = self.get('modulename', ())
            if not modulenames: modulenames = [self.oid]
            if modulenames: modulenames = self.modulename = ulistof(modulenames)

        if moduledirs:
            modules = []
            for modulename in modulenames:
                found = []
                for moduledir in moduledirs:
                    module = path.join(moduledir, modulename)
                    if path.exists(module):
                        found.append(module)
                        break
                if not found:
                    found = [path.join(moduledirs[0], modulename)]
                modules.extend(found)
        else:
            modules = modulenames
        if modules: modules = self.module = ulistof(modules)

        if not self.values:
            self.values = modules

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

def query_xuinst(*args):
    """afficher la commande pour déployer avec la commande $1 le module $2 sur
    l'hôte $3 dans le profil $4

    $1 peut valoir 'ruinst' ou 'uinst' ou être un chemin vers l'un de ces deux
    scripts

    $2 peut être
    * un nom de module: tout les modules de ce nom sont sélectionnés
    * un chemin complet: si un module avec le chemin complet est trouvé, ne
      sélectinner que celui-là, sinon faire comme si on n'avait spécifié que le
      nom du module
    * non spécifié: tout les modules devant être déployé sur l'hôte sont
      cherchés

    $3 peut être
    * un nom d'hôte: tous les hôtes de ce nom sont sélectionés
    * un nom d'hôte pleinement qualifié: si le nom d'hôte pleinement qualifié
      est trouvé, ne sélectionner que celui-là, sinon faire comme si on n'avait
      spécifié que le nom d'hôte
    * non spécifié: tous les hôtes vers lequel doit être déployé le module sont
      cherchés

    $4 peut valoir
    * 'NONE': seuls les déploiements sans profils définis sont sélectionnés.
      c'est la valeur par défaut.
    * 'ALL' ou '': ne pas tenir compte du profil lors de la sélection des
      modules et des hôtes
    * toute autre valeur, e.g prod ou test: seuls les déploiement de ce profil
      sont sélectionnés
    Il est possible de spécifier plusieurs profils en les séparant par des
    virgules. Par exemple, 'prod,NONE' permet de sélectionner les déploiements
    sans profil ou dans le profil 'prod'

    le module, ou l'hôte, ou les deux sont requis. le profil est facultatif.

    Les valeurs $5..$* sont des définitions d'attributs utilisées pour mettre à
    jour les faits trouvés. Ces définitions sont utilisés comme argument de
    uinst.
    """
    xuinst = args[0] if args[0:1] else None
    if xuinst is None:
        verb = None
    elif xuinst == 'ruinst' or xuinst.endswith('/ruinst'):
        verb = 'ruinst'
    elif xuinst == 'uinst' or  xuinst.endswith('/uinst'):
        verb = 'uinst'
    else:
        verb = None
    if verb is None:
        raise ValueError("Le verbe est requis et doit être 'uinst' ou 'ruinst'")
    qmodule = args[1:2] and args[1] or None
    qhost = args[2:3] and args[2] or None
    qprofile = args[3] if args[3:4] else 'NONE'
    supplattrs = args[4:]

    if not qmodule and not qhost:
        raise ValueError("Il faut spécifier module et/ou host")

    if not qmodule:
        modules = None
    elif cwithpath(qmodule):
        qmodule = path.abspath(qmodule)
        modules = catalog.find_objects('module', expr=module_matcher(qmodule))
        if not modules:
            modules = catalog.find_objects('module', expr=modulename_matcher(qmodule))
    else:
        modules = catalog.find_objects('module', expr=modulename_matcher(qmodule))

    if not qhost:
        hosts = None
    else:
        if cwithpath(qhost):
            qhost = path.basename(path.abspath(qhost))
        if withdomain(qhost):
            hosts = catalog.find_objects('host', expr=host_matcher(qhost))
            if not hosts:
                hosts = catalog.find_objects('host', expr=hostname_matcher(qhost))
        else:
            hosts = catalog.find_objects('host', expr=hostname_matcher(qhost))

    if qprofile == '': qprofile = 'ALL'
    qprofiles = flattenstr([qprofile])
    if 'ALL' in qprofiles:
        qprofile = None
    else:
        expr = []
        for qprofile in qprofiles:
            if qprofile == 'NONE':
                qprofile = NONE(EXISTS('profile'))
            else:
                qprofile = dict(profile=qprofile)
            expr.append(qprofile)
        qprofile = ANY(*expr)

    # modules et hosts sont spécifiés
    if modules is not None and hosts is not None:
        facts = catalog.find_facts(
            verb=verb,
            tsotype='module', tsexpr=dict(oid=[module.oid for module in modules]),
            ttotype='host', ttexpr=dict(oid=[host.oid for host in hosts]),
            expr=qprofile,
        )

    # Seuls les modules sont spécifiés: chercher les hôtes
    elif modules is not None:
        facts = catalog.find_facts(
            verb=verb,
            tsotype='module', tsexpr=dict(oid=[module.oid for module in modules]),
            ttotype='host',
            expr=qprofile,
        )

    # Seuls les hôtes sont spécifiés: chercher les modules
    elif hosts is not None:
        facts = catalog.find_facts(
            verb=verb,
            tsotype='module',
            ttotype='host', ttexpr=dict(oid=[host.oid for host in hosts]),
            expr=qprofile,
        )

    # afficher la commande
    if supplattrs: parser = Parser()
    for fact, tsobjects, ttobjects in facts:
        hs = flattenseq([host.host for host in ttobjects])
        ms = flattenseq([module.module for module in tsobjects])
        if supplattrs: parser.parse_attrs(supplattrs, fact)
        vars = []
        for name, values in fact.attrs.items():
            vars.append("%s=%s" % (name, qshell(':'.join(values))))
        for m in ms:
            # préférer si possible le chemin fourni par l'utilisateur
            if withpath(qmodule): m = qmodule
            if fact.verb == 'uinst':
                # chaque hôte est traité à part avec uinst:rsync
                for h in hs:
                    parts = [xuinst, '--no-deploydb', '-yh', qshell(h), qshell(m)]
                    if vars:
                        parts.extend(['--', ' '.join(vars)])
                    print ' '.join(parts)
            elif fact.verb == 'ruinst':
                hs = ':'.join(hs)
                parts = [xuinst, '--no-deploydb', '-h', qshell(hs), qshell(m), '--', '-y']
                if vars: parts.append(' '.join(vars))
                print ' '.join(parts)
