# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import logging
import random
from typing import List, Text

from rasa_core_sdk import Action, Tracker
from rasa_core_sdk.events import SlotSet, EventType

logger = logging.getLogger(__name__)

# this slot is used to store information needed
# to do the form handling, needs to be part
# of the domain
FORM_SLOT_NAME = "requested_slot"


class FormField(object):

    # noinspection PyMethodMayBeStatic
    def validate(self, value):
        """Check if extracted value for a requested slot is valid.

        Users should override this to implement custom validation logic,
        returning None indicates a negative validation result, and the slot
        will not be set.
        """
        return value


class EntityFormField(FormField):

    def __init__(self, entity_name, slot_name):
        self.entity_name = entity_name
        self.slot_name = slot_name

    def extract(self, tracker):
        # type: (Tracker) -> List[EventType]

        value = next(tracker.get_latest_entity_values(self.entity_name), None)
        validated = self.validate(value)
        if validated is not None:
            return [SlotSet(self.slot_name, validated)]
        else:
            return []


class BooleanFormField(FormField):
    """A form field that prompts the user for a yes or no answer.
    The interpreter should map positive and negative answers to
    the intents ``affirm_intent`` and ``deny_intent``.
    """

    def __init__(self, slot_name, affirm_intent, deny_intent):
        self.slot_name = slot_name
        self.affirm_intent = affirm_intent
        self.deny_intent = deny_intent

    def extract(self, tracker):
        # type: (Tracker) -> List[EventType]

        intent = tracker.latest_message.get("intent", {}).get("name")
        if intent == self.affirm_intent:
            value = True
        elif intent == self.deny_intent:
            value = False
        else:
            return []

        return [SlotSet(self.slot_name, value)]


class FreeTextFormField(FormField):

    def __init__(self, slot_name):
        self.slot_name = slot_name

    def extract(self, tracker):
        # type: (Tracker) -> List[EventType]

        validated = self.validate(tracker.latest_message.get("text"))
        if validated is not None:
            return [SlotSet(self.slot_name, validated)]
        return []


class FormAction(Action):
    def name(self):
        # type: () -> Text
        """Unique identifier of this form action."""

        raise NotImplementedError

    RANDOMIZE = True

    @staticmethod
    def required_fields():
        return []

    @staticmethod
    def should_request_slot(tracker, slot_name):
        existing_val = tracker.get_slot(slot_name)
        return existing_val is None

    def get_other_slots(self, tracker):
        # type: (Tracker) -> List[EventType]

        requested_slot = tracker.get_slot(FORM_SLOT_NAME)

        requested_entity = None
        for f in self.required_fields():
            if f.slot_name == requested_slot:
                requested_entity = getattr(f, 'entity_name', None)

        slot_events = []
        extracted_entities = {requested_entity}

        for f in self.required_fields():
            if (isinstance(f, EntityFormField)
                    and f.slot_name != requested_slot
                    and f.entity_name not in extracted_entities):
                slot_events.extend(f.extract(tracker))
                extracted_entities.add(f.entity_name)
        return slot_events

    def get_requested_slot(self, tracker):
        # type: (Tracker) -> List[EventType]

        requested_slot = tracker.get_slot(FORM_SLOT_NAME)

        required = self.required_fields()

        if self.RANDOMIZE:
            random.shuffle(required)

        if requested_slot is None:
            return []
        else:
            fields = [f
                      for f in required
                      if f.slot_name == requested_slot]

            if len(fields) == 1:
                return fields[0].extract(tracker)
            else:
                logger.debug("Unable to extract value "
                             "for requested slot: {}".format(requested_slot))
                return []

    def run(self, dispatcher, tracker, domain):

        events = (self.get_requested_slot(tracker) +
                  self.get_other_slots(tracker))

        temp_tracker = tracker.copy()
        for e in events:
            temp_tracker.slots[e["name"]] = e["value"]

        for field in self.required_fields():
            if self.should_request_slot(temp_tracker, field.slot_name):

                dispatcher.utter_template(
                        "utter_ask_{}".format(field.slot_name),
                        tracker)

                events.append(SlotSet(FORM_SLOT_NAME, field.slot_name))
                return events

        # there is nothing more to request, so we can submit
        events_from_submit = self.submit(dispatcher, temp_tracker, domain) or []

        return events + events_from_submit

    def submit(self, dispatcher, tracker, domain):
        raise NotImplementedError(
                "a form action must implement a submit method")
