#
# Copyright 2013 Rackspace Hosting.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import abc

import six


@six.add_metaclass(abc.ABCMeta)
class TraitPluginBase(object):
    """Base class for plugins.

    It converts notification fields to Trait values.
    """

    def __init__(self, **kw):
        """Setup the trait plugin.

        For each Trait definition a plugin is used on in a conversion
        definition, a new instance of the plugin will be created, and
        initialized with the parameters (if any) specified in the
        config file.

        :param kw: the parameters specified in the event definitions file.

        """
        super(TraitPluginBase, self).__init__()

    @abc.abstractmethod
    def trait_value(self, match_list):
        """Convert a set of fields to a Trait value.

        This method is called each time a trait is attempted to be extracted
        from a notification. It will be called *even if* no matching fields
        are found in the notification (in that case, the match_list will be
        empty). If this method returns None, the trait *will not* be added to
        the event. Any other value returned by this method will be used as
        the value for the trait. Values returned will be coerced to the
        appropriate type for the trait.

        :param match_list: A list (may be empty if no matches) of *tuples*.
          Each tuple is (field_path, value) where field_path is the jsonpath
          for that specific field.

        Example::

            trait's fields definition: ['payload.foobar',
                                        'payload.baz',
                                        'payload.thing.*']
            notification body:
                        {
                         'message_id': '12345',
                         'publisher': 'someservice.host',
                         'payload': {
                                     'foobar': 'test',
                                     'thing': {
                                               'bar': 12,
                                               'boing': 13,
                                              }
                                    }
                        }
            match_list will be: [('payload.foobar','test'),
                                 ('payload.thing.bar',12),
                                 ('payload.thing.boing',13)]

        Here is a plugin that emulates the default (no plugin) behavior:

        .. code-block:: python

          class DefaultPlugin(TraitPluginBase):
              "Plugin that returns the first field value."

              def __init__(self, **kw):
                  super(DefaultPlugin, self).__init__()

              def trait_value(self, match_list):
                  if not match_list:
                      return None
                  return match_list[0][1]
        """


class SplitterTraitPlugin(TraitPluginBase):
    """Plugin that splits a piece off of a string value."""

    def __init__(self, separator=".", segment=0, max_split=None, **kw):
        """Setup how do split the field.

        :param  separator:  String to split on. default "."
        :param  segment:    Which segment to return. (int) default 0
        :param  max_split: Limit number of splits. Default: None (no limit)
        """
        self.separator = separator
        self.segment = segment
        self.max_split = max_split
        super(SplitterTraitPlugin, self).__init__(**kw)

    def trait_value(self, match_list):
        if not match_list:
            return None
        value = six.text_type(match_list[0][1])
        if self.max_split is not None:
            values = value.split(self.separator, self.max_split)
        else:
            values = value.split(self.separator)
        try:
            return values[self.segment]
        except IndexError:
            return None


class BitfieldTraitPlugin(TraitPluginBase):
    """Plugin to set flags on a bitfield."""
    def __init__(self, initial_bitfield=0, flags=None, **kw):
        """Setup bitfield trait.

        :param initial_bitfield: (int) initial value for the bitfield
                                 Flags that are set will be OR'ed with this.
        :param flags: List of dictionaries defining bitflags to set depending
                      on data in the notification. Each one has the following
                      keys:
                            path: jsonpath of field to match.
                            bit: (int) number of bit to set (lsb is bit 0)
                            value: set bit if corresponding field's value
                                   matches this. If value is not provided,
                                   bit will be set if the field exists (and
                                   is non-null), regardless of it's value.

        """
        self.initial_bitfield = initial_bitfield
        if flags is None:
            flags = []
        self.flags = flags
        super(BitfieldTraitPlugin, self).__init__(**kw)

    def trait_value(self, match_list):
        matches = dict(match_list)
        bitfield = self.initial_bitfield
        for flagdef in self.flags:
            path = flagdef['path']
            bit = 2 ** int(flagdef['bit'])
            if path in matches:
                if 'value' in flagdef:
                    if matches[path] == flagdef['value']:
                        bitfield |= bit
                else:
                    bitfield |= bit
        return bitfield

[((21, 1, 21, 31), 'six.add_metaclass', 'six.add_metaclass', ({(21, 19, 21, 30): 'abc.ABCMeta'}, {}), '(abc.ABCMeta)', False, 'import six\n'), ((113, 16, 113, 47), 'six.text_type', 'six.text_type', ({(113, 30, 113, 46): 'match_list[0][1]'}, {}), '(match_list[0][1])', False, 'import six\n')]