import json
import logging
import re

from platypush.config import Config
from platypush.message.event import Event, EventMatchResult
from platypush.message.request import Request
from platypush.utils import get_event_class_by_type


def parse(msg):
    """ Builds a dict given another dictionary or
        a JSON UTF-8 encoded string/bytearray """

    if isinstance(msg, bytes) or isinstance(msg, bytearray):
        msg = msg.decode('utf-8')
    if isinstance(msg, str):
        try:
            msg = json.loads(msg.strip())
        except:
            logging.warning('Invalid JSON message: {}'.format(msg))
            return None

    return msg


class EventCondition(object):
    """ Event hook condition class """

    def __init__(self, type=Event.__class__, **kwargs):
        """
        Rule constructor.
        Params:
            type   -- Class of the event to be built
            kwargs -- Fields rules as a key-value (e.g. source_button=btn_id
                or recognized_phrase='Your phrase')
        """

        self.type = type
        self.args = {}
        self.parsed_args = {}

        for (key, value) in kwargs.items():
            # TODO So far we only allow simple value match. If value is a dict
            # instead, we should allow more a sophisticated attribute matching,
            # e.g. or conditions, in, and other operators.
            self.args[key] = value

    @classmethod
    def build(cls, rule):
        """ Builds a rule given either another EventRule, a dictionary or
            a JSON UTF-8 encoded string/bytearray """

        if isinstance(rule, cls): return rule
        else: rule = parse(rule)
        assert isinstance(rule, dict)

        type = get_event_class_by_type(
            rule.pop('type') if 'type' in rule else 'Event')

        args = {}
        for (key, value) in rule.items():
            args[key] = value

        return cls(type=type, **args)


class EventAction(Request):
    """ Event hook action class. It is a special type of runnable request
        whose fields can be configured later depending on the event context """

    def __init__(self, target=None, action=None, **args):
        if target is None: target=Config.get('device_id')
        super().__init__(target=target, action=action, **args)


    def execute(self, **context):
        event_args = context.pop('event').args if 'event' in context else {}

        for (argname, value) in self.args.items():
            if isinstance(value, str):
                parsed_value = ''
                while value:
                    m = re.match('([^\\\]*)\$([\w\d_-]+)(.*)', value)
                    if m:
                        context_argname = m.group(2)
                        value = m.group(3)
                        if context_argname in context:
                            parsed_value += m.group(1) + context[context_argname]
                        else:
                            parsed_value += m.group(1) + '$' + m.group(2)
                    else:
                        parsed_value += value
                        value = ''

                value = parsed_value

            self.args[argname] = value

        super().execute()


    @classmethod
    def build(cls, action):
        action = super().parse(action)
        action['origin'] = Config.get('device_id')

        if 'target' not in action:
            action['target'] = action['origin']
        return super().build(action)


class EventHook(object):
    """ Event hook class. It consists of one conditionss and
        one or multiple actions to be executed """

    def __init__(self, name, condition=None, actions=[]):
        """ Construtor. Takes a name, a EventCondition object and a list of
            EventAction objects as input """

        self.name = name
        self.condition = EventCondition.build(condition or {})
        self.actions = actions


    @classmethod
    def build(cls, name, hook):
        """ Builds a rule given either another EventRule, a dictionary or
            a JSON UTF-8 encoded string/bytearray """

        if isinstance(hook, cls): return hook
        else: hook = parse(hook)
        assert isinstance(hook, dict)

        condition = EventCondition.build(hook['if']) if 'if' in hook else None
        actions = []
        if 'then' in hook:
            if isinstance(hook['then'], list):
                actions = [EventAction.build(action) for action in hook['then']]
            else:
                actions = [EventAction.build(hook['then'])]

        return cls(name=name, condition=condition, actions=actions)


    def matches_event(self, event):
        """ Returns an EventMatchResult object containing the information
            about the match between the event and this hook """

        return event.matches_condition(self.condition)


    def run(self, event):
        """ Checks the condition of the hook against a particular event and
            runs the hook actions if the condition is met """

        result = self.matches_event(event)

        if result.is_match:
            logging.info('Running hook {} triggered by an event'.format(self.name))

            for action in self.actions:
                action.execute(event=event, **result.parsed_args)


# vim:sw=4:ts=4:et:

