# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from abc import ABC, abstractmethod


from botbuilder.core import NullTelemetryClient, BotTelemetryClient
from .dialog import Dialog
from .dialog_context import DialogContext
from .dialog_event import DialogEvent
from .dialog_events import DialogEvents
from .dialog_set import DialogSet


class DialogContainer(Dialog, ABC):
    def __init__(self, dialog_id: str = None):
        super().__init__(dialog_id)

        self.dialogs = DialogSet()

    @property
    def telemetry_client(self) -> BotTelemetryClient:
        """
        Gets the telemetry client for logging events.
        """
        return self._telemetry_client

    @telemetry_client.setter
    def telemetry_client(self, value: BotTelemetryClient) -> None:
        """
        Sets the telemetry client for all dialogs in this set.
        """
        if value is None:
            self._telemetry_client = NullTelemetryClient()
        else:
            self._telemetry_client = value

        # Care! Dialogs.TelemetryClient assignment internally assigns the
        # TelemetryClient for each dialog which could lead to an eventual stack
        # overflow in cyclical dialog structures.
        # Don't set the telemetry client if the candidate instance is the same as
        # the currently set one.
        if self.dialogs.telemetry_client != value:
            self.dialogs.telemetry_client = self._telemetry_client

    @abstractmethod
    def create_child_context(self, dialog_context: DialogContext) -> DialogContext:
        raise NotImplementedError()

    def find_dialog(self, dialog_id: str) -> Dialog:
        # TODO: deprecate DialogSet.find
        return self.dialogs.find_dialog(dialog_id)

    async def on_dialog_event(
        self, dialog_context: DialogContext, dialog_event: DialogEvent
    ) -> bool:
        """
        Called when an event has been raised, using `DialogContext.emitEvent()`, by either the current dialog or a
         dialog that the current dialog started.
        :param dialog_context: The dialog context for the current turn of conversation.
        :param dialog_event: The event being raised.
        :return: True if the event is handled by the current dialog and bubbling should stop.
        """
        handled = await super().on_dialog_event(dialog_context, dialog_event)

        # Trace unhandled "versionChanged" events.
        if not handled and dialog_event.name == DialogEvents.version_changed:

            trace_message = (
                f"Unhandled dialog event: {dialog_event.name}. Active Dialog: "
                f"{dialog_context.active_dialog.id}"
            )

            await dialog_context.context.send_trace_activity(trace_message)

        return handled

    def get_internal_version(self) -> str:
        """
        GetInternalVersion - Returns internal version identifier for this container.
        DialogContainers detect changes of all sub-components in the container and map that to an DialogChanged event.
        Because they do this, DialogContainers "hide" the internal changes and just have the .id. This isolates changes
        to the container level unless a container doesn't handle it.  To support this DialogContainers define a
        protected virtual method GetInternalVersion() which computes if this dialog or child dialogs have changed
        which is then examined via calls to check_for_version_change_async().
        :return: version which represents the change of the internals of this container.
        """
        return self.dialogs.get_version()

    async def check_for_version_change_async(self, dialog_context: DialogContext):
        """
        :param dialog_context: dialog context.
        :return: task.
        Checks to see if a containers child dialogs have changed since the current dialog instance
        was started.

        This should be called at the start of `beginDialog()`, `continueDialog()`, and `resumeDialog()`.
        """
        current = dialog_context.active_dialog.version
        dialog_context.active_dialog.version = self.get_internal_version()

        # Check for change of previously stored hash
        if current and current != dialog_context.active_dialog.version:
            # Give bot an opportunity to handle the change.
            # - If bot handles it the changeHash will have been updated as to avoid triggering the
            #   change again.
            await dialog_context.emit_event(
                DialogEvents.version_changed, self.id, True, False
            )
