#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# Basic trigger host, will be imported from any other
# Contains a TriggerFactory, which records the host functions
# decorated with @record.
# Contains a trigger which calls good functions from factory.
#
# License   : New BSD License
# Date      : 10/03/2015
# Copyright : Pierre-Elliott Bécue <becue@crans.org>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the <organization> nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""This module provides host functions for trigger, such as the TriggerFactory which
stores parsers and services metadata."""

import collections
import functools

import gestion.config.trigger as trigger_config
from gestion.trigger.producer import EventProducer

# Clogger
import cranslib.clogger as clogger

LOGGER = clogger.CLogger("trigger", "host.py/ack", trigger_config.log_level, trigger_config.debug)

PRODUCER = EventProducer("trigger.civet")

# *
# *             Factory
# *

class TriggerFactory(object):
    """The TriggerFactory is designed to store services and parsers.

     * services are functions decorated with record_service, and, possibly,
       chaining. Such functions are designed to do some operations on
       servers in order to take into account LDAP modifications (or,
       potentially, whatever else).

     * parsers are functions decorated with record parsers, which are
       triggered by some keywords (eg, ldap attributes names whose values
       have been modified). They return stuff to do, and a service to
       call.

    """

    _services = {}
    _parsers = collections.defaultdict(list)

    @classmethod
    def register_service(cls, key, value):
        """Stores the appropriate service in the factory"""
        cls._services[key] = value

    @classmethod
    def get_service(cls, key):
        """Retrieves the appropriate service"""
        return cls._services.get(key, None)

    @classmethod
    def get_services(cls):
        """Retrieves the list of all services"""
        return cls._services.values()

    @classmethod
    def register_parser(cls, keys, parser):
        """Stores the attributes to watch and the function"""
        for key in keys:
            if not isinstance(key, str) and not isinstance(key, unicode):
                key = getattr(key, 'ldap_name', None)
            if key is not None:
                cls._parsers[key].append(parser)
            else:
                LOGGER.debug("Problem when recording parser %r on keys %r.", parser.func_name, keys)

    @classmethod
    def get_parser(cls, keyword):
        """Restitutes the parser using keywords"""
        return cls._parsers[keyword]

# *
# *             Factory manipulation
# *

def record_service(ack=True):
    """Records a service in the TriggerFactory.

    It uses its name as a key for reference, the value being
    the function itself.

    """
    def enhance_func(func):
        """Creates an enhanced function which tests if ack is True and
        creates an ack if it's the case."""
        @functools.wraps(func)
        def enhanced_func(*args, **kwargs):
            """Dummy"""
            # The first arg is ob_id, execpt if kwargs.
            if args:
                __ob_id = args[0]
            else:
                __ob_id = kwargs['ob_id']

            # The function does not return.
            func(*args, **kwargs)

            LOGGER.debug("[%r] Ran %r on (%r, %r)", __ob_id, func.func_name, args, kwargs,)

            if ack:
                # We send directly with routing key trigger.ack on the way.
                # Thus, ack service does not need any parser.
                routing_key = "ack"
                body = (__ob_id, func.func_name)
                LOGGER.debug("[%r] Ack %r.", __ob_id, body)
                PRODUCER.send_message("trigger.%s" % (routing_key,), body)
        TriggerFactory.register_service(func.func_name, enhanced_func)
        return enhanced_func
    return enhance_func

def trigger_service(what):
    """Calls the appropriate service"""
    return TriggerFactory.get_service(what)

def record_parser(*watched):
    """Stores the function in TriggerFactory, using args as
    keys for the dict"""

    def find_parser(func):
        """Adds the chaining_pos at the end of the return of functions."""
        @functools.wraps(func)
        def enhanced_func(*args, **kwargs):
            """dummy"""
            __ob_id = args[0]
            ret = func(*args, **kwargs)
            LOGGER.debug("[%r] In record_parser.find_parser, ran %r(%r, %r). Got %r.", __ob_id, func.func_name, args, kwargs, ret)
            if ret is not None:
                ret = [elem for elem in ret] + [getattr(func, "chaining_pos", 0)]
                LOGGER.debug("[%r] In record_parser.find_parser, for %r got chaining_pos %r", __ob_id, func.func_name, ret[-1])
            return ret
        TriggerFactory.register_parser(watched, enhanced_func)
        return enhanced_func

    return find_parser

def chaining(pos):
    """Allows chaining of operations, by adding a position marker
    on the function."""

    def add_pos(func):
        """Adds the chaining_pos variable to func"""
        LOGGER.debug("%r chaining pos : %r", func.func_name, pos)
        setattr(func, "chaining_pos", pos)
        return func

    return add_pos
