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

__all__ = (
    'Wobundle',
    'wobundle_matcher', 'wobundlename_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

################################################################################
# Wobundle

def match_wobundle(qwobundle, object):
    if withpath(qwobundle): # wobundle avec chemin
        return qwobundle in object.get('wobundle', ())
    else: # nom de wobundle
        return qwobundle in object.get('wobundlename', ())
def wobundle_matcher(qwobundle):
    return lambda object: match_wobundle(qwobundle, object)

def match_wobundlename(qwobundle, object):
    qwobundle = path.basename(qwobundle)
    return qwobundle in object.get('wobundlename', ())
def wobundlename_matcher(qwobundle):
    return lambda object: match_wobundlename(qwobundle, object)

class Wobundle(Object):
    ATTRS = XT(Object,
               values=pathP, wobundle=mpathP, wobundledir=pathP,
               basedir=pathP, dirspec=fileP)

    FILESPECS = ['*.woa', '*.framework']

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

        wobundles = self.get('wobundle', [])
        basedir = self.get('basedir', None)
        if basedir is not None:
            wobundles.extend(self.resolve_basedir(basedir, dirs=True, filespec=self.FILESPECS))
        dirspec = self.get('dirspec', None)
        if dirspec is not None:
            wobundles.extend(self.resolve_filespec(dirspec, dirs=True))

        if wobundles:
            # générer wobundledir et wobundlename à partir de wobundle
            wobundledirs = [path.dirname(wobundle) for wobundle in wobundles]
            if wobundledirs: wobundledirs = self.wobundledir = ulistof(wobundledirs)

            wobundlenames = [path.basename(wobundle) for wobundle in wobundles]
            if wobundlenames: wobundlenames = self.wobundlename = ulistof(wobundlenames)

        else:
            # générer wobundles à partir de wobundledir et wobundlename
            wobundledirs = self.get('wobundledir', ())
            if not wobundledirs and default is not None:
                wobundledirs = default.get('wobundledir', ())
            if wobundledirs: wobundledirs = self.wobundledir = ulistof(wobundledirs)

            wobundlenames = self.get('wobundlename', ())
            if not wobundlenames: wobundlenames = ['%s.woa' % self.oid]
            if wobundlenames: wobundlenames = self.wobundlename = ulistof(wobundlenames)

        if wobundledirs:
            wobundles = []
            for wobundlename in wobundlenames:
                found = []
                for wobundledir in wobundledirs:
                    wobundle = path.join(wobundledir, wobundlename)
                    if path.exists(wobundle):
                        found.append(wobundle)
                        break
                if not found:
                    found = [path.join(wobundledirs[0], wobundlename)]
                wobundles.extend(found)
        else:
            wobundles = wobundlenames
        if wobundles: wobundles = self.wobundle = ulistof(wobundles)

        if not self.values:
            self.values = wobundles

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

def option_choice(yesoption, nooption):
    def func(value):
        if istrue(value): return yesoption
        else: return nooption
    return func

def what_choice(value):
    if value in ('bundle',):
        return '--bundle'
    elif value in ('webres',):
        return '--webres'
    else:
        log.warning("invalid what value %s", value)
        return None

def restart_choice(value):
    if istrue(value) or value in ('restart', 'stop-start'):
        return '--stop-start'
    elif value in ('bounce',):
        return '--bounce'
    elif isfalse(value) or value in ('no-restart',):
        return '--no-restart'
    else:
        log.warning("invalid restart value %s", value)
        return None

WOINST_ATTRS = {
    'prefix': dict(
        func=option_choice('--prefix', '--no-prefix'),
    ), 'what': dict(
        func=what_choice,
    ), 'tag': dict(
        func=option_choice('--tag', '--no-tag'),
    ), 'dbconfig': dict(
        option='--active-dbconfig',
    ), 'dbconfig_map': dict(
        option='--active-dbconfig-map',
        multiple=True,
        flattensep=',',
    ), 'restart': dict(
        func=restart_choice,
    ), 'exec': dict(
        option='--exec',
        multiple=True,
    ),
}

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

    $1 doit valoir 'rwoinst' ou être un chemin vers ce script

    $2 peut être
    * un nom de wobundle: tout les wobundles de ce nom sont sélectionnés
    * un chemin complet: si un wobundle 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 wobundle
    * non spécifié: tout les wobundles 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 wobundle 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
      wobundles 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, 'NONE,prod' permet de sélectionner les déploiements
    sans profil ou dans le profil 'prod'

    le wobundle, 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. Les mappings suivants sont supportés:

      = attribut =             = option de woinst =
      what                     --bundle / --webres
      dbconfig                 --active-dbconfig
      dbconfig_map             --active-dbconfig-map
      restart                  --stop-start / --bounce
      exec                     --exec
      tag                      --tag / --no-tag
      prefix                   --prefix / --no-prefix
      destdir                  HTDOCSDIR=

    Les autres définitions sont utilisées comme argument de woinst, pour définir
    les valeurs des préfixes.
    """
    rwoinst = args[0] if args[0:1] else None
    if rwoinst is not None and (rwoinst == 'rwoinst' or rwoinst.endswith('/rwoinst')):
        verb = 'rwoinst'
    else:
        raise ValueError("Le verbe est requis et doit valoir 'rwoinst'")
    qwobundle = 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 qwobundle and not qhost:
        raise ValueError("Il faut spécifier wobundle et/ou host")

    if not qwobundle:
        wobundles = None
    elif cwithpath(qwobundle):
        qwobundle = path.abspath(qwobundle)
        wobundles = catalog.find_objects('wobundle', expr=wobundle_matcher(qwobundle))
        if not wobundles:
            wobundles = catalog.find_objects('wobundle', expr=wobundlename_matcher(qwobundle))
    else:
        wobundles = catalog.find_objects('wobundle', expr=wobundlename_matcher(qwobundle))

    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)

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

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

    # Seuls les hôtes sont spécifiés: chercher les wobundles
    elif hosts is not None:
        facts = catalog.find_facts(
            verb=verb,
            tsotype='wobundle',
            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:
        if supplattrs: parser.parse_attrs(supplattrs, fact)
        hs = ':'.join(flattenseq([host.host for host in ttobjects]))

        for wobundle in tsobjects:
            # construire les options de woinst. on prend les valeurs d'abord
            # dans le fait puis dans l'objet wobundle.
            options = []
            vars = []
            names = set(fact.attrs.keys())
            names.update(wobundle.attrs.keys())
            for name in names:
                values = fact.get(name, None)
                factvalue = True
                if values is None:
                    values = wobundle.get(name, None)
                    factvalue = False
                if values is None:
                    # ne devrait pas se produire en principe
                    continue
                if name in ('profile',):
                    # les attributs de sélection du profil ont été déjà été traités
                    # plus haut
                    continue
                elif name == 'destdir':
                    name = 'HTDOCSDIR'
                params = WOINST_ATTRS.get(name, None)
                if params is None:
                    if factvalue:
                        # les variables spécifiques ne sont prise que dans le
                        # fait.
                        vars.append("%s=%s" % (name, qshell(':'.join(values))))
                else:
                    func = params.get('func', None)
                    option = params.get('option', None)
                    if func is not None:
                        option = func(values[0])
                        if option is not None:
                            options.extend(listof(option))
                    elif option is not None:
                        if params.get('multiple', False):
                            flattensep = params.get('flattensep', None)
                            if flattensep is not None:
                                values = flattenstr(values, flattensep)
                            for value in values:
                                options.append(option)
                                options.append(qshell(value))
                        else:
                            options.append(option)
                            options.append(qshell(values[0]))
                    else:
                        raise ValueError("missing option key for attribute %s" % name)

            for w in wobundle.wobundle:
                # préférer si possible le chemin fourni par l'utilisateur
                if withpath(qwobundle): w = qwobundle
                parts = [rwoinst, '--no-deploydb', '-yh', qshell(hs), qshell(w)]
                if options or vars:
                    parts.append('--')
                    if options: parts.extend(options)
                    if vars: parts.extend(vars)
                print ' '.join(parts)
