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

__all__ = (
    'Webapp',
    'webapp_matcher', 'webappname_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

################################################################################
# Webapp

def match_webapp(qwebapp, object):
    if withpath(qwebapp): # webapp avec chemin
        return qwebapp in object.get('webapp', ())
    else: # nom de webapp
        return qwebapp in object.get('webappname', ())
def webapp_matcher(qwebapp):
    return lambda object: match_webapp(qwebapp, object)

def match_webappname(qwebapp, object):
    qwebapp = path.basename(qwebapp)
    return qwebapp in object.get('webappname', ())
def webappname_matcher(qwebapp):
    return lambda object: match_webappname(qwebapp, object)

class Webapp(Object):
    ATTRS = XT(Object,
               values=pathP, webapp=mpathP, webappdir=pathP,
               basedir=pathP, dirspec=fileP)

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

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

        if webapps:
            # générer webappdir et webappname à partir de webapp
            webappdirs = [path.dirname(webapp) for webapp in webapps]
            if webappdirs: webappdirs = self.webappdir = ulistof(webappdirs)

            webappnames = [path.basename(webapp) for webapp in webapps]
            if webappnames: webappnames = self.webappname = ulistof(webappnames)

        else:
            # générer webapps à partir de webappdir et webappname
            webappdirs = self.get('webappdir', ())
            if not webappdirs and default is not None:
                webappdirs = default.get('webappdir', ())
            if webappdirs: webappdirs = self.webappdir = ulistof(webappdirs)

            webappnames = self.get('webappname', ())
            if not webappnames: webappnames = [self.oid]
            if webappnames: webappnames = self.webappname = ulistof(webappnames)

        if webappdirs:
            webapps = []
            for webappname in webappnames:
                found = []
                for webappdir in webappdirs:
                    webapp = path.join(webappdir, webappname)
                    if path.exists(webapp):
                        found.append(webapp)
                        break
                if not found:
                    found = [path.join(webappdirs[0], webappname)]
                webapps.extend(found)
        else:
            webapps = webappnames
        if webapps: webapps = self.webapp = ulistof(webapps)

        if not self.values:
            self.values = webapps

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

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

def pffprofile_support(value, fact, webapp):
    pffprofile = fact.get('pffprofile', None)
    if pffprofile is None: pffprofile = webapp.get('pffprofile', None)
    if pffprofile is None: return None
    if value == 'ensure':
        return ['--ensure-pffprofile', pffprofile[0]]
    elif value == 'set':
        return ['--set-pffprofile', pffprofile[0]]

TOINST_ATTRS = {
    'tomcat_profile': dict(
        option='--config-profile',
    ), 'catalina_base': dict(
        option='--catalina-base',
    ), 'tomcat_user': dict(
        option='--tomcat-user',
    ), 'tomcat_group': dict(
        option='--tomcat-group',
    ), 'tomcat_version': dict(
        option='--tomcat-version',
    ), 'manager_url': dict(
        option='--manager-url',
    ), 'manager_user': dict(
        option='--manager-user',
    ), 'manager_password': dict(
        option='--manager-password',
    ), 'wamap': dict(
        option='--wamap',
        multiple=True,
        flattensep=',',
    ), 'exclude': dict(
        option='--exclude',
        multiple=True,
    ), 'exclude_override': dict(
        option='--replace-excludes',
        multiple=True,
    ), 'protect': dict(
        option='--protect',
        multiple=True,
    ), 'rsync_option': dict(
        option='--rsync-option',
        multiple=True,
    ), 'rsync_option_override': dict(
        option='--replace-rsync-options',
        multiple=True,
    ), 'backup': dict(
        func=option_choice('--backup', '--no-backup'),
    ), 'restart': dict(
        func=option_choice('--restart', '--no-restart'),
    ), 'legacy_sort': dict(
        func=option_choice('--legacy-sort', None),
    ), 'pffprofile_support': dict(
        func=pffprofile_support,
    ),
}

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

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

    $2 peut être
    * un nom de webapp: toutes les webapps de ce nom sont sélectionnés
    * un chemin complet: si une webapp avec le chemin complet est trouvée, ne
      sélectinner que celle-là, sinon faire comme si on n'avait spécifié que le
      nom de la webapp
    * non spécifié: toutes les webapps devant être déployé sur l'hôte sont
      cherchées

    $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 webapp 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
      webapps et des hôtes
    * toute autre valeur, e.g prod ou test: seuls les déploiements 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'

    $5 peut valoir
    * 'NONE': seuls les déploiement sans profils pff définis sont sélectionnés.
    * 'ALL' ou '': ne pas tenir compte du profil pff lors de la sélection des
      webapps et des hôtes. c'est la valeur par défaut.
    * toute autre valeur, e.g prod ou test: seuls les déploiements de ce profil
      pff sont sélectionnés.

    la webapp, ou l'hôte, ou les deux sont requis. le profil et le profil pff
    sont facultatifs.

    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 toinst =
      tomcat_profile           --config-profile
      catalina_base            --catalina-base
      tomcat_user              --tomcat-user
      tomcat_group             --tomcat-group
      tomcat_version           --tomcat-version
      manager_url              --manager-url
      manager_user             --manager-user
      manager_password         --manager-password
      wamap                    --wamap
      exclude                  --exclude
      exclude_override         --replace-excludes
      protect                  --protect
      rsync_option             --rsync-option
      rsync_option_override    --replace-rsync-options
      backup                   --backup / --no-backup
      restart                  --restart / --no-restart
      legacy_sort              --legacy-sort
      pffprofile_support       --ensure-pffprofile / --set-pffprofile
    """
    rtoinst = args[0] if args[0:1] else None
    if rtoinst is not None and (rtoinst == 'rtoinst' or rtoinst.endswith('/rtoinst')):
        verb = 'rtoinst'
    else:
        raise ValueError("Le verbe est requis et doit valoir 'rtoinst'")
    qwebapp = 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'
    qpffprofile = args[4] if args[4:5] else 'ALL'
    supplattrs = args[5:]

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

    if not qwebapp:
        webapps = None
    elif cwithpath(qwebapp):
        qwebapp = path.abspath(qwebapp)
        webapps = catalog.find_objects('webapp', expr=webapp_matcher(qwebapp))
        if not webapps:
            webapps = catalog.find_objects('webapp', expr=webappname_matcher(qwebapp))
    else:
        webapps = catalog.find_objects('webapp', expr=webappname_matcher(qwebapp))

    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)

    if qpffprofile == '': qpffprofile = 'ALL'
    qpffprofiles = flattenstr([qpffprofile])
    if 'ALL' in qpffprofiles:
        qpffprofile = None
    else:
        expr = []
        for qpffprofile in qpffprofiles:
            if qpffprofile == 'NONE':
                qpffprofile = NONE(EXISTS('pffprofile'))
            else:
                qpffprofile = dict(pffprofile=qpffprofile)
            expr.append(qpffprofile)
        qpffprofile = ANY(*expr)

    if qprofile is None and qpffprofile is None:
        expr = None
    elif qprofile is not None and qpffprofile is not None:
        expr = ALL(qprofile, qpffprofile)
    elif qprofile is not None:
        expr = qprofile
    elif qpffprofile is not None:
        expr = qpffprofile

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

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

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

    # 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 webapp in tsobjects:
            # construire les options de toinst. on prend les valeurs d'abord dans le
            # fait puis dans l'objet webapp.
            options = []
            names = set(fact.attrs.keys())
            names.update(webapp.attrs.keys())
            for name in names:
                values = fact.get(name, None)
                factvalue = True
                if values is None:
                    values = webapp.get(name, None)
                    factvalue = False
                if values is None:
                    # ne devrait pas se produire en principe
                    continue
                if name in ('profile', 'pffprofile'):
                    # les attributs de sélection du profil ont été déjà été traités
                    # plus haut
                    continue
                params = TOINST_ATTRS.get(name, None)
                if params is None:
                    if factvalue:
                        log.warning("ignoring %s option %s=%r", fact.verb, name, values)
                else:
                    func = params.get('func', None)
                    option = params.get('option', None)
                    if func is not None:
                        option = func(values[0], fact, webapp)
                        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 webapp.webapp:
                # préférer si possible le chemin fourni par l'utilisateur
                if withpath(qwebapp): w = qwebapp
                parts = [rtoinst, '--no-deploydb', '-yh', qshell(hs), qshell(w)]
                if options:
                    parts.append('--')
                    parts.extend(options)
                print ' '.join(parts)
