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

from typing import Dict
from botbuilder.core import (
                            TurnContext
                            )
from .dialog import Dialog
from .dialog_set import DialogSet
from .dialog_context import DialogContext
from .dialog_turn_result import DialogTurnResult
from .dialog_state import DialogState
from .dialog_turn_status import DialogTurnStatus
from .dialog_reason import DialogReason
from .dialog_set import DialogSet
from .dialog_instance import DialogInstance

class ComponentDialog(Dialog):
    persisted_dialog_state = "dialogs"

    def __init__(self, dialog_id: str):
        super(ComponentDialog, self).__init__(dialog_id)

        if dialog_id is None:
            raise TypeError('ComponentDialog(): dialog_id cannot be None.')

        self._dialogs = DialogSet()

    # TODO: Add TelemetryClient

    @property
    def initial_dialog_id(self) -> str:
        """Gets the ID of the initial dialog id.

        :param:
        :return str:ID of the dialog this instance is for.
        """
        return self._id

    @initial_dialog_id.setter
    def initial_dialog_id(self, value: str) -> None:
        """Sets the ID of the initial dialog id.

        :param value: ID of the dialog this instance is for.
        :return:
        """
        self._id = value

    async def begin_dialog(self, outer_dc: DialogContext, options: object = None) -> DialogTurnResult:
        if outer_dc is None:
            raise TypeError('ComponentDialog.begin_dialog(): outer_dc cannot be None.')

        # Start the inner dialog.
        dialog_state = DialogState()
        outer_dc.active_dialog.state[self.persisted_dialog_state] = dialog_state
        inner_dc = DialogContext(self._dialogs, outer_dc.context, dialog_state)
        inner_dc.parent = outer_dc
        turn_result = await self.on_begin_dialog(inner_dc, options)

        # Check for end of inner dialog
        if turn_result.status != DialogTurnStatus.Waiting:
            # Return result to calling dialog
            return await self.end_component(outer_dc, turn_result.result)
        else:
            # Just signal waiting
            return Dialog.end_of_turn

    async def continue_dialog(self, outer_dc: DialogContext) -> DialogTurnResult:
        if outer_dc is None:
            raise TypeError('ComponentDialog.begin_dialog(): outer_dc cannot be None.')
        # Continue execution of inner dialog.
        dialog_state = outer_dc.active_dialog.state[self.persisted_dialog_state]
        inner_dc = DialogContext(self._dialogs, outer_dc.context, dialog_state)
        inner_dc.parent = outer_dc
        turn_result = await self.on_continue_dialog(inner_dc)

        if turn_result.status != DialogTurnStatus.Waiting:
            return await self.end_component(outer_dc, turn_result.result)
        else:
            return Dialog.end_of_turn

    async def resume_dialog(self, outer_dc: DialogContext, reason: DialogReason, result: object = None) -> DialogTurnResult:
        # Containers are typically leaf nodes on the stack but the dev is free to push other dialogs
        # on top of the stack which will result in the container receiving an unexpected call to
        # resume_dialog() when the pushed on dialog ends.
        # To avoid the container prematurely ending we need to implement this method and simply
        # ask our inner dialog stack to re-prompt.
        await self.reprompt_dialog(outer_dc.context, outer_dc.active_dialog)
        return Dialog.end_of_turn

    async def reprompt_dialog(self, turn_context: TurnContext, instance: DialogInstance) -> None:
        # Delegate to inner dialog.
        dialog_state = instance.state[self.persisted_dialog_state]
        inner_dc = DialogContext(self._dialogs, turn_context, dialog_state)
        await inner_dc.reprompt_dialog()

        # Notify component
        await self.on_reprompt_dialog(turn_context, instance)

    async def end_dialog(self, turn_context: TurnContext, instance: DialogInstance, reason: DialogReason) -> None:
        # Forward cancel to inner dialogs
        if reason == DialogReason.CancelCalled:
            dialog_state = instance.State[self.persisted_dialog_state]
            inner_dc = DialogContext(self._dialogs, turn_context, dialog_state)
            await inner_dc.cancel_all_dialogs()
        await self.on_end_dialog(turn_context, instance, reason)

    def add_dialog(self, dialog: Dialog) -> object:
        """
        Adds a dialog to the component dialog.
        Adding a new dialog will inherit the BotTelemetryClient of the ComponentDialog.
        :param dialog: The dialog to add.
        :return: The updated ComponentDialog
        """
        self._dialogs.add(dialog)
        if not self.initial_dialog_id:
            self.initial_dialog_id = dialog.id
        return self

    def find_dialog(self, dialog_id: str ) -> Dialog:
        """
        Finds a dialog by ID.
        Adding a new dialog will inherit the BotTelemetryClient of the ComponentDialog.
        :param dialog_id: The dialog to add.
        :return: The dialog; or None if there is not a match for the ID.
        """
        return self._dialogs.find(dialog_id)

    async def on_begin_dialog(self, inner_dc: DialogContext, options: object) -> DialogTurnResult:
        return inner_dc.begin_dialog(self.initial_dialog_id, options)

    async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult:
        return inner_dc.continue_dialog()

    async def on_end_dialog(self, context: TurnContext, instance: DialogInstance, reason: DialogReason) -> None:
        return

    async def on_reprompt_dialog(self, turn_context: TurnContext, instance: DialogInstance) -> None:
        return

    async def end_component(self, outer_dc: DialogContext, result: object) -> DialogTurnResult:
        return outer_dc.end_dialog(result)


from botbuilder.schema import Activity

from .emulator_validation import EmulatorValidation
from .channel_validation import ChannelValidation
from .microsoft_app_credentials import MicrosoftAppCredentials
from .credential_provider import CredentialProvider
from .claims_identity import ClaimsIdentity

class JwtTokenValidation:

    @staticmethod
    async def authenticate_request(activity: Activity, auth_header: str, credentials: CredentialProvider) -> ClaimsIdentity:
        """Authenticates the request and sets the service url in the set of trusted urls.

        :param activity: The incoming Activity from the Bot Framework or the Emulator
        :type activity: ~botframework.connector.models.Activity
        :param auth_header: The Bearer token included as part of the request
        :type auth_header: str
        :param credentials: The set of valid credentials, such as the Bot Application ID
        :type credentials: CredentialProvider

        :raises Exception:
        """
        if not auth_header:
            # No auth header was sent. We might be on the anonymous code path.
            is_auth_disabled = await credentials.is_authentication_disabled()
            if is_auth_disabled:
                # We are on the anonymous code path.
                return

            # No Auth Header. Auth is required. Request is not authorized.
            raise Exception('Unauthorized Access. Request is not authorized')

        claims_identity = await JwtTokenValidation.validate_auth_header(auth_header, credentials, activity.channel_id, activity.service_url)

        # On the standard Auth path, we need to trust the URL that was incoming.
        MicrosoftAppCredentials.trust_service_url(activity.service_url)

        return claims_identity

    @staticmethod
    async def validate_auth_header(auth_header: str, credentials: CredentialProvider, channel_id: str, service_url: str = None) -> ClaimsIdentity:
        if not auth_header:
            raise ValueError('argument auth_header is null')
        using_emulator = EmulatorValidation.is_token_from_emulator(auth_header)
        if using_emulator:
            return await EmulatorValidation.authenticate_emulator_token(auth_header, credentials, channel_id)
        else:
            if service_url:
                return await ChannelValidation.authenticate_token_service_url(auth_header, credentials, service_url, channel_id)
            else:
                return await ChannelValidation.authenticate_token(auth_header, credentials, channel_id)


"""
"wheel" copyright (c) 2012-2017 Daniel Holth <dholth@fastmail.fm> and
contributors.

The MIT License

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Create a Azure wheel (.whl) distribution (a wheel is a built archive format).

This file is a copy of the official bdist_wheel file from wheel 0.30.0a0, enhanced
of the bottom with some Microsoft extension for Azure SDK for Python

"""

import csv
import hashlib
import os
import subprocess
import warnings
import shutil
import json
import sys

try:
    import sysconfig
except ImportError:  # pragma nocover
    # Python < 2.7
    import distutils.sysconfig as sysconfig

import pkg_resources

safe_name = pkg_resources.safe_name
safe_version = pkg_resources.safe_version

from shutil import rmtree
from email.generator import Generator

from distutils.core import Command
from distutils.sysconfig import get_python_version

from distutils import log as logger

from wheel.pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag, get_platform
from wheel.util import native, open_for_csv
from wheel.archive import archive_wheelfile
from wheel.pkginfo import read_pkg_info, write_pkg_info
from wheel.metadata import pkginfo_to_dict
from wheel import pep425tags, metadata
from wheel import __version__ as wheel_version

def safer_name(name):
    return safe_name(name).replace('-', '_')

def safer_version(version):
    return safe_version(version).replace('-', '_')

class bdist_wheel(Command):

    description = 'create a wheel distribution'

    user_options = [('bdist-dir=', 'b',
                     "temporary directory for creating the distribution"),
                    ('plat-name=', 'p',
                     "platform name to embed in generated filenames "
                     "(default: %s)" % get_platform()),
                    ('keep-temp', 'k',
                     "keep the pseudo-installation tree around after " +
                     "creating the distribution archive"),
                    ('dist-dir=', 'd',
                     "directory to put final built distributions in"),
                    ('skip-build', None,
                     "skip rebuilding everything (for testing/debugging)"),
                    ('relative', None,
                     "build the archive using relative paths"
                     "(default: false)"),
                    ('owner=', 'u',
                     "Owner name used when creating a tar file"
                     " [default: current user]"),
                    ('group=', 'g',
                     "Group name used when creating a tar file"
                     " [default: current group]"),
                    ('universal', None,
                     "make a universal wheel"
                     " (default: false)"),
                    ('python-tag=', None,
                     "Python implementation compatibility tag"
                     " (default: py%s)" % get_impl_ver()[0]),
                    ]

    boolean_options = ['keep-temp', 'skip-build', 'relative', 'universal']

    def initialize_options(self):
        self.bdist_dir = None
        self.data_dir = None
        self.plat_name = None
        self.plat_tag = None
        self.format = 'zip'
        self.keep_temp = False
        self.dist_dir = None
        self.distinfo_dir = None
        self.egginfo_dir = None
        self.root_is_pure = None
        self.skip_build = None
        self.relative = False
        self.owner = None
        self.group = None
        self.universal = False
        self.python_tag = 'py' + get_impl_ver()[0]
        self.plat_name_supplied = False

    def finalize_options(self):
        if self.bdist_dir is None:
            bdist_base = self.get_finalized_command('bdist').bdist_base
            self.bdist_dir = os.path.join(bdist_base, 'wheel')

        self.data_dir = self.wheel_dist_name + '.data'
        self.plat_name_supplied = self.plat_name is not None

        need_options = ('dist_dir', 'plat_name', 'skip_build')

        self.set_undefined_options('bdist',
                                   *zip(need_options, need_options))

        self.root_is_pure = not (self.distribution.has_ext_modules()
                                 or self.distribution.has_c_libraries())

        # Support legacy [wheel] section for setting universal
        wheel = self.distribution.get_option_dict('wheel')
        if 'universal' in wheel:
            # please don't define this in your global configs
            val = wheel['universal'][1].strip()
            if val.lower() in ('1', 'true', 'yes'):
                self.universal = True

    @property
    def wheel_dist_name(self):
        """Return distribution full name with - replaced with _"""
        return '-'.join((safer_name(self.distribution.get_name()),
                         safer_version(self.distribution.get_version())))

    def get_tag(self):
        # bdist sets self.plat_name if unset, we should only use it for purepy
        # wheels if the user supplied it.
        if self.plat_name_supplied:
            plat_name = self.plat_name
        elif self.root_is_pure:
            plat_name = 'any'
        else:
            plat_name = self.plat_name or get_platform()
            if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647:
                plat_name = 'linux_i686'
        plat_name = plat_name.replace('-', '_').replace('.', '_')

        if self.root_is_pure:
            if self.universal:
                impl = 'py2.py3'
            else:
                impl = self.python_tag
            tag = (impl, 'none', plat_name)
        else:
            impl_name = get_abbr_impl()
            impl_ver = get_impl_ver()
            # PEP 3149
            abi_tag = str(get_abi_tag()).lower()
            tag = (impl_name + impl_ver, abi_tag, plat_name)
            supported_tags = pep425tags.get_supported(
                supplied_platform=plat_name if self.plat_name_supplied else None)
            # XXX switch to this alternate implementation for non-pure:
            assert tag == supported_tags[0], "%s != %s" % (tag, supported_tags[0])
        return tag

    def get_archive_basename(self):
        """Return archive name without extension"""

        impl_tag, abi_tag, plat_tag = self.get_tag()

        archive_basename = "%s-%s-%s-%s" % (
            self.wheel_dist_name,
            impl_tag,
            abi_tag,
            plat_tag)
        return archive_basename

    def run(self):
        build_scripts = self.reinitialize_command('build_scripts')
        build_scripts.executable = 'python'

        if not self.skip_build:
            self.run_command('build')

        install = self.reinitialize_command('install',
                                            reinit_subcommands=True)
        install.root = self.bdist_dir
        install.compile = False
        install.skip_build = self.skip_build
        install.warn_dir = False

        # A wheel without setuptools scripts is more cross-platform.
        # Use the (undocumented) `no_ep` option to setuptools'
        # install_scripts command to avoid creating entry point scripts.
        install_scripts = self.reinitialize_command('install_scripts')
        install_scripts.no_ep = True

        # Use a custom scheme for the archive, because we have to decide
        # at installation time which scheme to use.
        for key in ('headers', 'scripts', 'data', 'purelib', 'platlib'):
            setattr(install,
                    'install_' + key,
                    os.path.join(self.data_dir, key))

        basedir_observed = ''

        if os.name == 'nt':
            # win32 barfs if any of these are ''; could be '.'?
            # (distutils.command.install:change_roots bug)
            basedir_observed = os.path.normpath(os.path.join(self.data_dir, '..'))
            self.install_libbase = self.install_lib = basedir_observed

        setattr(install,
                'install_purelib' if self.root_is_pure else 'install_platlib',
                basedir_observed)

        logger.info("installing to %s", self.bdist_dir)

        self.run_command('install')

        archive_basename = self.get_archive_basename()

        pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
        if not self.relative:
            archive_root = self.bdist_dir
        else:
            archive_root = os.path.join(
                self.bdist_dir,
                self._ensure_relative(install.install_base))

        self.set_undefined_options(
            'install_egg_info', ('target', 'egginfo_dir'))
        self.distinfo_dir = os.path.join(self.bdist_dir,
                                         '%s.dist-info' % self.wheel_dist_name)
        self.egg2dist(self.egginfo_dir,
                      self.distinfo_dir)

        self.write_wheelfile(self.distinfo_dir)

        self.write_record(self.bdist_dir, self.distinfo_dir)

        # Make the archive
        if not os.path.exists(self.dist_dir):
            os.makedirs(self.dist_dir)
        wheel_name = archive_wheelfile(pseudoinstall_root, archive_root)

        # Sign the archive
        if 'WHEEL_TOOL' in os.environ:
            subprocess.call([os.environ['WHEEL_TOOL'], 'sign', wheel_name])

        # Add to 'Distribution.dist_files' so that the "upload" command works
        getattr(self.distribution, 'dist_files', []).append(
            ('bdist_wheel', get_python_version(), wheel_name))

        if not self.keep_temp:
            if self.dry_run:
                logger.info('removing %s', self.bdist_dir)
            else:
                rmtree(self.bdist_dir)

    def write_wheelfile(self, wheelfile_base, generator='bdist_wheel (' + wheel_version + ')'):
        from email.message import Message
        msg = Message()
        msg['Wheel-Version'] = '1.0'  # of the spec
        msg['Generator'] = generator
        msg['Root-Is-Purelib'] = str(self.root_is_pure).lower()

        # Doesn't work for bdist_wininst
        impl_tag, abi_tag, plat_tag = self.get_tag()
        for impl in impl_tag.split('.'):
            for abi in abi_tag.split('.'):
                for plat in plat_tag.split('.'):
                    msg['Tag'] = '-'.join((impl, abi, plat))

        wheelfile_path = os.path.join(wheelfile_base, 'WHEEL')
        logger.info('creating %s', wheelfile_path)
        with open(wheelfile_path, 'w') as f:
            Generator(f, maxheaderlen=0).flatten(msg)

    def _ensure_relative(self, path):
        # copied from dir_util, deleted
        drive, path = os.path.splitdrive(path)
        if path[0:1] == os.sep:
            path = drive + path[1:]
        return path

    def _pkginfo_to_metadata(self, egg_info_path, pkginfo_path):
        return metadata.pkginfo_to_metadata(egg_info_path, pkginfo_path)

    def license_file(self):
        """Return license filename from a license-file key in setup.cfg, or None."""
        metadata = self.distribution.get_option_dict('metadata')
        if not 'license_file' in metadata:
            return None
        return metadata['license_file'][1]

    def setupcfg_requirements(self):
        """Generate requirements from setup.cfg as
        ('Requires-Dist', 'requirement; qualifier') tuples. From a metadata
        section in setup.cfg:

        [metadata]
        provides-extra = extra1
            extra2
        requires-dist = requirement; qualifier
            another; qualifier2
            unqualified

        Yields

        ('Provides-Extra', 'extra1'),
        ('Provides-Extra', 'extra2'),
        ('Requires-Dist', 'requirement; qualifier'),
        ('Requires-Dist', 'another; qualifier2'),
        ('Requires-Dist', 'unqualified')
        """
        metadata = self.distribution.get_option_dict('metadata')

        # our .ini parser folds - to _ in key names:
        for key, title in (('provides_extra', 'Provides-Extra'),
                           ('requires_dist', 'Requires-Dist')):
            if not key in metadata:
                continue
            field = metadata[key]
            for line in field[1].splitlines():
                line = line.strip()
                if not line:
                    continue
                yield (title, line)

    def add_requirements(self, metadata_path):
        """Add additional requirements from setup.cfg to file metadata_path"""
        additional = list(self.setupcfg_requirements())
        if not additional: return
        pkg_info = read_pkg_info(metadata_path)
        if 'Provides-Extra' in pkg_info or 'Requires-Dist' in pkg_info:
            warnings.warn('setup.cfg requirements overwrite values from setup.py')
            del pkg_info['Provides-Extra']
            del pkg_info['Requires-Dist']
        for k, v in additional:
            pkg_info[k] = v
        write_pkg_info(metadata_path, pkg_info)

    def egg2dist(self, egginfo_path, distinfo_path):
        """Convert an .egg-info directory into a .dist-info directory"""
        def adios(p):
            """Appropriately delete directory, file or link."""
            if os.path.exists(p) and not os.path.islink(p) and os.path.isdir(p):
                shutil.rmtree(p)
            elif os.path.exists(p):
                os.unlink(p)

        adios(distinfo_path)

        if not os.path.exists(egginfo_path):
            # There is no egg-info. This is probably because the egg-info
            # file/directory is not named matching the distribution name used
            # to name the archive file. Check for this case and report
            # accordingly.
            import glob
            pat = os.path.join(os.path.dirname(egginfo_path), '*.egg-info')
            possible = glob.glob(pat)
            err = "Egg metadata expected at %s but not found" % (egginfo_path,)
            if possible:
                alt = os.path.basename(possible[0])
                err += " (%s found - possible misnamed archive file?)" % (alt,)

            raise ValueError(err)

        if os.path.isfile(egginfo_path):
            # .egg-info is a single file
            pkginfo_path = egginfo_path
            pkg_info = self._pkginfo_to_metadata(egginfo_path, egginfo_path)
            os.mkdir(distinfo_path)
        else:
            # .egg-info is a directory
            pkginfo_path = os.path.join(egginfo_path, 'PKG-INFO')
            pkg_info = self._pkginfo_to_metadata(egginfo_path, pkginfo_path)

            # ignore common egg metadata that is useless to wheel
            shutil.copytree(egginfo_path, distinfo_path,
                            ignore=lambda x, y: set(('PKG-INFO',
                                                     'requires.txt',
                                                     'SOURCES.txt',
                                                     'not-zip-safe',)))

            # delete dependency_links if it is only whitespace
            dependency_links_path = os.path.join(distinfo_path, 'dependency_links.txt')
            with open(dependency_links_path, 'r') as dependency_links_file:
                dependency_links = dependency_links_file.read().strip()
            if not dependency_links:
                adios(dependency_links_path)

        write_pkg_info(os.path.join(distinfo_path, 'METADATA'), pkg_info)

        # XXX deprecated. Still useful for current distribute/setuptools.
        metadata_path = os.path.join(distinfo_path, 'METADATA')
        self.add_requirements(metadata_path)

        # XXX intentionally a different path than the PEP.
        metadata_json_path = os.path.join(distinfo_path, 'metadata.json')
        pymeta = pkginfo_to_dict(metadata_path,
                                 distribution=self.distribution)

        if 'description' in pymeta:
            description_filename = 'DESCRIPTION.rst'
            description_text = pymeta.pop('description')
            description_path = os.path.join(distinfo_path,
                                            description_filename)
            with open(description_path, "wb") as description_file:
                description_file.write(description_text.encode('utf-8'))
            pymeta['extensions']['python.details']['document_names']['description'] = description_filename

        # XXX heuristically copy any LICENSE/LICENSE.txt?
        license = self.license_file()
        if license:
            license_filename = 'LICENSE.txt'
            shutil.copy(license, os.path.join(self.distinfo_dir, license_filename))
            pymeta['extensions']['python.details']['document_names']['license'] = license_filename

        with open(metadata_json_path, "w") as metadata_json:
            json.dump(pymeta, metadata_json, sort_keys=True)

        adios(egginfo_path)

    def write_record(self, bdist_dir, distinfo_dir):
        from wheel.util import urlsafe_b64encode

        record_path = os.path.join(distinfo_dir, 'RECORD')
        record_relpath = os.path.relpath(record_path, bdist_dir)

        def walk():
            for dir, dirs, files in os.walk(bdist_dir):
                dirs.sort()
                for f in sorted(files):
                    yield os.path.join(dir, f)

        def skip(path):
            """Wheel hashes every possible file."""
            return (path == record_relpath)

        with open_for_csv(record_path, 'w+') as record_file:
            writer = csv.writer(record_file)
            for path in walk():
                relpath = os.path.relpath(path, bdist_dir)
                if skip(relpath):
                    hash = ''
                    size = ''
                else:
                    with open(path, 'rb') as f:
                        data = f.read()
                    digest = hashlib.sha256(data).digest()
                    hash = 'sha256=' + native(urlsafe_b64encode(digest))
                    size = len(data)
                record_path = os.path.relpath(
                    path, bdist_dir).replace(os.path.sep, '/')
                writer.writerow((record_path, hash, size))

#-------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
#--------------------------------------------------------------------------

from distutils import log as logger
import os.path

#from wheel.bdist_wheel import bdist_wheel
class azure_bdist_wheel(bdist_wheel):

    description = "Create an Azure wheel distribution"

    user_options = bdist_wheel.user_options + \
        [('azure-namespace-package=', None,
            "Name of the deepest nspkg used")]

    def initialize_options(self):
        bdist_wheel.initialize_options(self)
        self.azure_namespace_package = None

    def finalize_options(self):
        bdist_wheel.finalize_options(self)
        if self.azure_namespace_package and not self.azure_namespace_package.endswith("-nspkg"):
            raise ValueError("azure_namespace_package must finish by -nspkg")

    def run(self):
        if not self.distribution.install_requires:
            self.distribution.install_requires = []
        self.distribution.install_requires.append(
            "{}>=2.0.0".format(self.azure_namespace_package))
        bdist_wheel.run(self)

    def write_record(self, bdist_dir, distinfo_dir):
        if self.azure_namespace_package:
            # Split and remove last part, assuming it's "nspkg"
            subparts = self.azure_namespace_package.split('-')[0:-1]
        folder_with_init = [os.path.join(*subparts[0:i+1]) for i in range(len(subparts))]
        for azure_sub_package in folder_with_init:
            init_file = os.path.join(bdist_dir, azure_sub_package, '__init__.py')
            if os.path.isfile(init_file):
                logger.info("manually remove {} while building the wheel".format(init_file))
                os.remove(init_file)
            else:
                raise ValueError("Unable to find {}. Are you sure of your namespace package?".format(init_file))
        bdist_wheel.write_record(self, bdist_dir, distinfo_dir)
cmdclass = {
    'bdist_wheel': azure_bdist_wheel,
}


# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
#
# Code generated by Microsoft (R) AutoRest Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is
# regenerated.
# --------------------------------------------------------------------------

from msrest.pipeline import ClientRawResponse
from msrest.exceptions import HttpOperationError

from .. import models

class ConversationsOperations(object):
    """ConversationsOperations operations.

    You should not instantiate directly this class, but create a Client instance that will create it for you and attach it as attribute.

    :param client: Client for service requests.
    :param config: Configuration of service client.
    :param serializer: An object model serializer.
    :param deserializer: An object model deserializer.
    :ivar api_version: The API version to use for the request. Constant value: "v3".
    """

    models = models

    def __init__(self, client, config, serializer, deserializer):

        self._client = client
        self._serialize = serializer
        self._deserialize = deserializer

        self.config = config
        self.api_version = "v3"

    def get_conversations(
            self, continuation_token=None, custom_headers=None, raw=False, **operation_config):
        """GetConversations.

        List the Conversations in which this bot has participated.
        GET from this method with a skip token
        The return value is a ConversationsResult, which contains an array of
        ConversationMembers and a skip token.  If the skip token is not empty,
        then
        there are further values to be returned. Call this method again with
        the returned token to get more values.
        Each ConversationMembers object contains the ID of the conversation and
        an array of ChannelAccounts that describe the members of the
        conversation.

        :param continuation_token: skip or continuation token
        :type continuation_token: str
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ConversationsResult or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ConversationsResult or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.get_conversations.metadata['url']

        # Construct parameters
        query_parameters = {}
        if continuation_token is not None:
            query_parameters['continuationToken'] = self._serialize.query("continuation_token", continuation_token, 'str')

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct and send request
        request = self._client.get(url, query_parameters, header_parameters)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ConversationsResult', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    get_conversations.metadata = {'url': '/v3/conversations'}

    def create_conversation(
            self, parameters, custom_headers=None, raw=False, **operation_config):
        """CreateConversation.

        Create a new Conversation.
        POST to this method with a
        * Bot being the bot creating the conversation
        * IsGroup set to true if this is not a direct message (default is
        false)
        * Array containing the members to include in the conversation
        The return value is a ResourceResponse which contains a conversation id
        which is suitable for use
        in the message payload and REST API uris.
        Most channels only support the semantics of bots initiating a direct
        message conversation.  An example of how to do that would be:
        ```
        var resource = await connector.conversations.CreateConversation(new
        ConversationParameters(){ Bot = bot, members = new ChannelAccount[] {
        new ChannelAccount("user1") } );
        await connect.Conversations.SendToConversationAsync(resource.Id, new
        Activity() ... ) ;
        ```.

        :param parameters: Parameters to create the conversation from
        :type parameters:
         ~botframework.connector.models.ConversationParameters
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ConversationResourceResponse or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ConversationResourceResponse or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.create_conversation.metadata['url']

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        header_parameters['Content-Type'] = 'application/json; charset=utf-8'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct body
        body_content = self._serialize.body(parameters, 'ConversationParameters')

        # Construct and send request
        request = self._client.post(url, query_parameters, header_parameters, body_content)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 201, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ConversationResourceResponse', response)
        if response.status_code == 201:
            deserialized = self._deserialize('ConversationResourceResponse', response)
        if response.status_code == 202:
            deserialized = self._deserialize('ConversationResourceResponse', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    create_conversation.metadata = {'url': '/v3/conversations'}

    def send_to_conversation(
            self, conversation_id, activity, custom_headers=None, raw=False, **operation_config):
        """SendToConversation.

        This method allows you to send an activity to the end of a
        conversation.
        This is slightly different from ReplyToActivity().
        * SendToConversation(conversationId) - will append the activity to the
        end of the conversation according to the timestamp or semantics of the
        channel.
        * ReplyToActivity(conversationId,ActivityId) - adds the activity as a
        reply to another activity, if the channel supports it. If the channel
        does not support nested replies, ReplyToActivity falls back to
        SendToConversation.
        Use ReplyToActivity when replying to a specific activity in the
        conversation.
        Use SendToConversation in all other cases.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param activity: Activity to send
        :type activity: ~botframework.connector.models.Activity
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ResourceResponse or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ResourceResponse or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.send_to_conversation.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        header_parameters['Content-Type'] = 'application/json; charset=utf-8'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct body
        body_content = self._serialize.body(activity, 'Activity')

        # Construct and send request
        request = self._client.post(url, query_parameters, header_parameters, body_content)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 201, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 201:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 202:
            deserialized = self._deserialize('ResourceResponse', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    send_to_conversation.metadata = {'url': '/v3/conversations/{conversationId}/activities'}

    def send_conversation_history(
            self, conversation_id, activities=None, custom_headers=None, raw=False, **operation_config):
        """SendConversationHistory.

        This method allows you to upload the historic activities to the
        conversation.
        Sender must ensure that the historic activities have unique ids and
        appropriate timestamps. The ids are used by the client to deal with
        duplicate activities and the timestamps are used by the client to
        render the activities in the right order.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param activities: A collection of Activities that conforms to the
         Transcript schema.
        :type activities: list[~botframework.connector.models.Activity]
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ResourceResponse or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ResourceResponse or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        history = models.Transcript(activities=activities)

        # Construct URL
        url = self.send_conversation_history.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        header_parameters['Content-Type'] = 'application/json; charset=utf-8'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct body
        body_content = self._serialize.body(history, 'Transcript')

        # Construct and send request
        request = self._client.post(url, query_parameters, header_parameters, body_content)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 201, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 201:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 202:
            deserialized = self._deserialize('ResourceResponse', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    send_conversation_history.metadata = {'url': '/v3/conversations/{conversationId}/activities/history'}

    def update_activity(
            self, conversation_id, activity_id, activity, custom_headers=None, raw=False, **operation_config):
        """UpdateActivity.

        Edit an existing activity.
        Some channels allow you to edit an existing activity to reflect the new
        state of a bot conversation.
        For example, you can remove buttons after someone has clicked "Approve"
        button.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param activity_id: activityId to update
        :type activity_id: str
        :param activity: replacement Activity
        :type activity: ~botframework.connector.models.Activity
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ResourceResponse or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ResourceResponse or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.update_activity.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str'),
            'activityId': self._serialize.url("activity_id", activity_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        header_parameters['Content-Type'] = 'application/json; charset=utf-8'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct body
        body_content = self._serialize.body(activity, 'Activity')

        # Construct and send request
        request = self._client.put(url, query_parameters, header_parameters, body_content)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 201, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 201:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 202:
            deserialized = self._deserialize('ResourceResponse', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    update_activity.metadata = {'url': '/v3/conversations/{conversationId}/activities/{activityId}'}

    def reply_to_activity(
            self, conversation_id, activity_id, activity, custom_headers=None, raw=False, **operation_config):
        """ReplyToActivity.

        This method allows you to reply to an activity.
        This is slightly different from SendToConversation().
        * SendToConversation(conversationId) - will append the activity to the
        end of the conversation according to the timestamp or semantics of the
        channel.
        * ReplyToActivity(conversationId,ActivityId) - adds the activity as a
        reply to another activity, if the channel supports it. If the channel
        does not support nested replies, ReplyToActivity falls back to
        SendToConversation.
        Use ReplyToActivity when replying to a specific activity in the
        conversation.
        Use SendToConversation in all other cases.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param activity_id: activityId the reply is to (OPTIONAL)
        :type activity_id: str
        :param activity: Activity to send
        :type activity: ~botframework.connector.models.Activity
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ResourceResponse or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ResourceResponse or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.reply_to_activity.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str'),
            'activityId': self._serialize.url("activity_id", activity_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        header_parameters['Content-Type'] = 'application/json; charset=utf-8'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct body
        body_content = self._serialize.body(activity, 'Activity')

        # Construct and send request
        request = self._client.post(url, query_parameters, header_parameters, body_content)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 201, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 201:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 202:
            deserialized = self._deserialize('ResourceResponse', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    reply_to_activity.metadata = {'url': '/v3/conversations/{conversationId}/activities/{activityId}'}

    def delete_activity(
            self, conversation_id, activity_id, custom_headers=None, raw=False, **operation_config):
        """DeleteActivity.

        Delete an existing activity.
        Some channels allow you to delete an existing activity, and if
        successful this method will remove the specified activity.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param activity_id: activityId to delete
        :type activity_id: str
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: None or ClientRawResponse if raw=true
        :rtype: None or ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.delete_activity.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str'),
            'activityId': self._serialize.url("activity_id", activity_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct and send request
        request = self._client.delete(url, query_parameters, header_parameters)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        if raw:
            client_raw_response = ClientRawResponse(None, response)
            return client_raw_response
    delete_activity.metadata = {'url': '/v3/conversations/{conversationId}/activities/{activityId}'}

    def get_conversation_members(
            self, conversation_id, custom_headers=None, raw=False, **operation_config):
        """GetConversationMembers.

        Enumerate the members of a conversation.
        This REST API takes a ConversationId and returns an array of
        ChannelAccount objects representing the members of the conversation.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: list or ClientRawResponse if raw=true
        :rtype: list[~botframework.connector.models.ChannelAccount] or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.get_conversation_members.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct and send request
        request = self._client.get(url, query_parameters, header_parameters)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('[ChannelAccount]', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    get_conversation_members.metadata = {'url': '/v3/conversations/{conversationId}/members'}

    def get_conversation_paged_members(
            self, conversation_id, page_size=None, continuation_token=None, custom_headers=None, raw=False, **operation_config):
        """GetConversationPagedMembers.

        Enumerate the members of a conversation one page at a time.
        This REST API takes a ConversationId. Optionally a pageSize and/or
        continuationToken can be provided. It returns a PagedMembersResult,
        which contains an array
        of ChannelAccounts representing the members of the conversation and a
        continuation token that can be used to get more values.
        One page of ChannelAccounts records are returned with each call. The
        number of records in a page may vary between channels and calls. The
        pageSize parameter can be used as
        a suggestion. If there are no additional results the response will not
        contain a continuation token. If there are no members in the
        conversation the Members will be empty or not present in the response.
        A response to a request that has a continuation token from a prior
        request may rarely return members from a previous request.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param page_size: Suggested page size
        :type page_size: int
        :param continuation_token: Continuation Token
        :type continuation_token: str
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: PagedMembersResult or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.PagedMembersResult or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`HttpOperationError<msrest.exceptions.HttpOperationError>`
        """
        # Construct URL
        url = self.get_conversation_paged_members.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}
        if page_size is not None:
            query_parameters['pageSize'] = self._serialize.query("page_size", page_size, 'int')
        if continuation_token is not None:
            query_parameters['continuationToken'] = self._serialize.query("continuation_token", continuation_token, 'str')

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct and send request
        request = self._client.get(url, query_parameters, header_parameters)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200]:
            raise HttpOperationError(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('PagedMembersResult', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    get_conversation_paged_members.metadata = {'url': '/v3/conversations/{conversationId}/pagedmembers'}

    def delete_conversation_member(
            self, conversation_id, member_id, custom_headers=None, raw=False, **operation_config):
        """DeleteConversationMember.

        Deletes a member from a conversation.
        This REST API takes a ConversationId and a memberId (of type string)
        and removes that member from the conversation. If that member was the
        last member
        of the conversation, the conversation will also be deleted.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param member_id: ID of the member to delete from this conversation
        :type member_id: str
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: None or ClientRawResponse if raw=true
        :rtype: None or ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.delete_conversation_member.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str'),
            'memberId': self._serialize.url("member_id", member_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct and send request
        request = self._client.delete(url, query_parameters, header_parameters)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 204]:
            raise models.ErrorResponseException(self._deserialize, response)

        if raw:
            client_raw_response = ClientRawResponse(None, response)
            return client_raw_response
    delete_conversation_member.metadata = {'url': '/v3/conversations/{conversationId}/members/{memberId}'}

    def get_activity_members(
            self, conversation_id, activity_id, custom_headers=None, raw=False, **operation_config):
        """GetActivityMembers.

        Enumerate the members of an activity.
        This REST API takes a ConversationId and a ActivityId, returning an
        array of ChannelAccount objects representing the members of the
        particular activity in the conversation.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param activity_id: Activity ID
        :type activity_id: str
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: list or ClientRawResponse if raw=true
        :rtype: list[~botframework.connector.models.ChannelAccount] or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.get_activity_members.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str'),
            'activityId': self._serialize.url("activity_id", activity_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct and send request
        request = self._client.get(url, query_parameters, header_parameters)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('[ChannelAccount]', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    get_activity_members.metadata = {'url': '/v3/conversations/{conversationId}/activities/{activityId}/members'}

    def upload_attachment(
            self, conversation_id, attachment_upload, custom_headers=None, raw=False, **operation_config):
        """UploadAttachment.

        Upload an attachment directly into a channel's blob storage.
        This is useful because it allows you to store data in a compliant store
        when dealing with enterprises.
        The response is a ResourceResponse which contains an AttachmentId which
        is suitable for using with the attachments API.

        :param conversation_id: Conversation ID
        :type conversation_id: str
        :param attachment_upload: Attachment data
        :type attachment_upload: ~botframework.connector.models.AttachmentData
        :param dict custom_headers: headers that will be added to the request
        :param bool raw: returns the direct response alongside the
         deserialized response
        :param operation_config: :ref:`Operation configuration
         overrides<msrest:optionsforoperations>`.
        :return: ResourceResponse or ClientRawResponse if raw=true
        :rtype: ~botframework.connector.models.ResourceResponse or
         ~msrest.pipeline.ClientRawResponse
        :raises:
         :class:`ErrorResponseException<botframework.connector.models.ErrorResponseException>`
        """
        # Construct URL
        url = self.upload_attachment.metadata['url']
        path_format_arguments = {
            'conversationId': self._serialize.url("conversation_id", conversation_id, 'str')
        }
        url = self._client.format_url(url, **path_format_arguments)

        # Construct parameters
        query_parameters = {}

        # Construct headers
        header_parameters = {}
        header_parameters['Accept'] = 'application/json'
        header_parameters['Content-Type'] = 'application/json; charset=utf-8'
        if custom_headers:
            header_parameters.update(custom_headers)

        # Construct body
        body_content = self._serialize.body(attachment_upload, 'AttachmentData')

        # Construct and send request
        request = self._client.post(url, query_parameters, header_parameters, body_content)
        response = self._client.send(request, stream=False, **operation_config)

        if response.status_code not in [200, 201, 202]:
            raise models.ErrorResponseException(self._deserialize, response)

        deserialized = None
        if response.status_code == 200:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 201:
            deserialized = self._deserialize('ResourceResponse', response)
        if response.status_code == 202:
            deserialized = self._deserialize('ResourceResponse', response)

        if raw:
            client_raw_response = ClientRawResponse(deserialized, response)
            return client_raw_response

        return deserialized
    upload_attachment.metadata = {'url': '/v3/conversations/{conversationId}/attachments'}


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

import traceback
import sys
from abc import ABC, abstractmethod
from typing import Dict
from enum import Enum

class TelemetryDataPointType(Enum):
        measurement = 0
        aggregation = 1

class BotTelemetryClient(ABC):
    @abstractmethod
    def track_pageview(self, name: str, url, duration: int = 0, properties : Dict[str, object]=None,
                        measurements: Dict[str, object]=None) -> None:
        """
        Send information about the page viewed in the application (a web page for instance).
        :param name: the name of the page that was viewed.
        :param url: the URL of the page that was viewed.
        :param duration: the duration of the page view in milliseconds. (defaults to: 0)
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)
        :param measurements: the set of custom measurements the client wants to attach to this data item. (defaults to: None)
        """
        raise NotImplementedError('BotTelemetryClient.track_request(): is not implemented.')

    @abstractmethod
    def track_exception(self, type: type = None, value : Exception =None, tb : traceback =None,
                        properties: Dict[str, object]=None, measurements: Dict[str, object]=None) -> None:
        """
        Send information about a single exception that occurred in the application.
        :param type: the type of the exception that was thrown.
        :param value: the exception that the client wants to send.
        :param tb: the traceback information as returned by :func:`sys.exc_info`.
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)
        :param measurements: the set of custom measurements the client wants to attach to this data item. (defaults to: None)
        """
        raise NotImplementedError('BotTelemetryClient.track_request(): is not implemented.')

    @abstractmethod
    def track_event(self, name: str, properties: Dict[str, object] = None,
                    measurements: Dict[str, object] = None) -> None:
        """
        Send information about a single event that has occurred in the context of the application.
        :param name: the data to associate to this event.
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)
        :param measurements: the set of custom measurements the client wants to attach to this data item. (defaults to: None)
        """
        raise NotImplementedError('BotTelemetryClient.track_event(): is not implemented.')

    @abstractmethod
    def track_metric(self, name: str, value: float, type: TelemetryDataPointType =None,
                    count: int =None, min: float=None, max: float=None, std_dev: float=None,
                    properties: Dict[str, object]=None) -> NotImplemented:
        """
        Send information about a single metric data point that was captured for the application.
        :param name: The name of the metric that was captured.
        :param value: The value of the metric that was captured.
        :param type: The type of the metric. (defaults to: TelemetryDataPointType.aggregation`)
        :param count: the number of metrics that were aggregated into this data point. (defaults to: None)
        :param min: the minimum of all metrics collected that were aggregated into this data point. (defaults to: None)
        :param max: the maximum of all metrics collected that were aggregated into this data point. (defaults to: None)
        :param std_dev: the standard deviation of all metrics collected that were aggregated into this data point. (defaults to: None)
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)
        """
        raise NotImplementedError('BotTelemetryClient.track_metric(): is not implemented.')

    @abstractmethod
    def track_trace(self, name, properties=None, severity=None):
        """
        Sends a single trace statement.
        :param name: the trace statement.\n
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)\n
        :param severity: the severity level of this trace, one of DEBUG, INFO, WARNING, ERROR, CRITICAL
        """
        raise NotImplementedError('BotTelemetryClient.track_trace(): is not implemented.')

    @abstractmethod
    def track_request(self, name: str, url: str, success: bool, start_time: str=None,
                    duration: int=None, response_code: str =None, http_method: str=None,
                    properties: Dict[str, object]=None, measurements: Dict[str, object]=None,
                    request_id: str=None):
        """
        Sends a single request that was captured for the application.
        :param name: The name for this request. All requests with the same name will be grouped together.
        :param url: The actual URL for this request (to show in individual request instances).
        :param success: True if the request ended in success, False otherwise.
        :param start_time: the start time of the request. The value should look the same as the one returned by :func:`datetime.isoformat()` (defaults to: None)
        :param duration: the number of milliseconds that this request lasted. (defaults to: None)
        :param response_code: the response code that this request returned. (defaults to: None)
        :param http_method: the HTTP method that triggered this request. (defaults to: None)
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)
        :param measurements: the set of custom measurements the client wants to attach to this data item. (defaults to: None)
        :param request_id: the id for this request. If None, a new uuid will be generated. (defaults to: None)
        """
        raise NotImplementedError('BotTelemetryClient.track_request(): is not implemented.')

    @abstractmethod
    def track_dependency(self, name:str, data:str, type:str=None, target:str=None, duration:int=None,
                        success:bool=None, result_code:str=None, properties:Dict[str, object]=None,
                        measurements:Dict[str, object]=None, dependency_id:str=None):
        """
        Sends a single dependency telemetry that was captured for the application.
        :param name: the name of the command initiated with this dependency call. Low cardinality value. Examples are stored procedure name and URL path template.
        :param data: the command initiated by this dependency call. Examples are SQL statement and HTTP URL with all query parameters.
        :param type: the dependency type name. Low cardinality value for logical grouping of dependencies and interpretation of other fields like commandName and resultCode. Examples are SQL, Azure table, and HTTP. (default to: None)
        :param target: the target site of a dependency call. Examples are server name, host address. (default to: None)
        :param duration: the number of milliseconds that this dependency call lasted. (defaults to: None)
        :param success: true if the dependency call ended in success, false otherwise. (defaults to: None)
        :param result_code: the result code of a dependency call. Examples are SQL error code and HTTP status code. (defaults to: None)
        :param properties: the set of custom properties the client wants attached to this data item. (defaults to: None)
        :param measurements: the set of custom measurements the client wants to attach to this data item. (defaults to: None)
        :param id: the id for this dependency call. If None, a new uuid will be generated. (defaults to: None)
        """
        raise NotImplementedError('BotTelemetryClient.track_dependency(): is not implemented.')


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

from typing import List, Union
from botbuilder.schema import (ActivityTypes, Activity, Attachment,
                               AttachmentLayoutTypes, CardAction, CardImage, SuggestedActions, InputHints)

def attachment_activity(attachment_layout: AttachmentLayoutTypes, attachments: List[Attachment], text: str = None,
                        speak: str = None, input_hint: Union[InputHints, str] = InputHints.accepting_input) -> Activity:
    message = Activity(type=ActivityTypes.message, attachment_layout=attachment_layout, attachments=attachments,
                       input_hint=input_hint)
    if text:
        message.text = text
    if speak:
        message.speak = speak
    return message

class MessageFactory:
    """
    A set of utility functions designed to assist with the formatting of the various message types a
    bot can return.
    """

    @staticmethod
    def text(text: str, speak: str = None, input_hint: Union[InputHints, str] = InputHints.accepting_input) -> Activity:
        """
        Returns a simple text message.

        :Example:
        message = MessageFactory.text('Greetings from example message')
        await context.send_activity(message)

        :param text:
        :param speak:
        :param input_hint:
        :return:
        """
        message = Activity(type=ActivityTypes.message, text=text, input_hint=input_hint)
        if speak:
            message.speak = speak

        return message

    @staticmethod
    def suggested_actions(actions: List[CardAction], text: str = None, speak: str = None,
                          input_hint: Union[InputHints, str] = InputHints.accepting_input) -> Activity:
        """
        Returns a message that includes a set of suggested actions and optional text.

        :Example:
        message = MessageFactory.suggested_actions([CardAction(title='a', type=ActionTypes.im_back, value='a'),
                                                    CardAction(title='b', type=ActionTypes.im_back, value='b'),
                                                    CardAction(title='c', type=ActionTypes.im_back, value='c')], 'Choose a color')
        await context.send_activity(message)

        :param actions:
        :param text:
        :param speak:
        :param input_hint:
        :return:
        """
        actions = SuggestedActions(actions=actions)
        message = Activity(type=ActivityTypes.message, input_hint=input_hint, suggested_actions=actions)
        if text:
            message.text = text
        if speak:
            message.speak = speak
        return message

    @staticmethod
    def attachment(attachment: Attachment, text: str = None, speak: str = None,
                   input_hint: Union[InputHints, str] = None):
        """
        Returns a single message activity containing an attachment.

        :Example:
        message = MessageFactory.attachment(CardFactory.hero_card(HeroCard(title='White T-Shirt',
                                                                  images=[CardImage(url='https://example.com/whiteShirt.jpg')],
                                                                  buttons=[CardAction(title='buy')])))
        await context.send_activity(message)

        :param attachment:
        :param text:
        :param speak:
        :param input_hint:
        :return:
        """
        return attachment_activity(AttachmentLayoutTypes.list, [attachment], text, speak, input_hint)

    @staticmethod
    def list(attachments: List[Attachment], text: str = None, speak: str = None,
             input_hint: Union[InputHints, str] = None) -> Activity:
        """
        Returns a message that will display a set of attachments in list form.

        :Example:
        message = MessageFactory.list([CardFactory.hero_card(HeroCard(title='title1',
                                                             images=[CardImage(url='imageUrl1')],
                                                             buttons=[CardAction(title='button1')])),
                                       CardFactory.hero_card(HeroCard(title='title2',
                                                             images=[CardImage(url='imageUrl2')],
                                                             buttons=[CardAction(title='button2')])),
                                       CardFactory.hero_card(HeroCard(title='title3',
                                                             images=[CardImage(url='imageUrl3')],
                                                             buttons=[CardAction(title='button3')]))])
        await context.send_activity(message)

        :param attachments:
        :param text:
        :param speak:
        :param input_hint:
        :return:
        """
        return attachment_activity(AttachmentLayoutTypes.list, attachments, text, speak, input_hint)

    @staticmethod
    def carousel(attachments: List[Attachment], text: str = None, speak: str = None,
                 input_hint: Union[InputHints, str] = None) -> Activity:
        """
        Returns a message that will display a set of attachments using a carousel layout.

        :Example:
        message = MessageFactory.carousel([CardFactory.hero_card(HeroCard(title='title1',
                                                                 images=[CardImage(url='imageUrl1')],
                                                                 buttons=[CardAction(title='button1')])),
                                           CardFactory.hero_card(HeroCard(title='title2',
                                                                 images=[CardImage(url='imageUrl2')],
                                                                 buttons=[CardAction(title='button2')])),
                                           CardFactory.hero_card(HeroCard(title='title3',
                                                                 images=[CardImage(url='imageUrl3')],
                                                                 buttons=[CardAction(title='button3')]))])
        await context.send_activity(message)

        :param attachments:
        :param text:
        :param speak:
        :param input_hint:
        :return:
        """
        return attachment_activity(AttachmentLayoutTypes.carousel, attachments, text, speak, input_hint)

    @staticmethod
    def content_url(url: str, content_type: str, name: str = None, text: str = None, speak: str = None,
                    input_hint: Union[InputHints, str] = None):
        """
        Returns a message that will display a single image or video to a user.

        :Example:
        message = MessageFactory.content_url('https://example.com/hawaii.jpg', 'image/jpeg',
                                             'Hawaii Trip', 'A photo from our family vacation.')
        await context.send_activity(message)

        :param url:
        :param content_type:
        :param name:
        :param text:
        :param speak:
        :param input_hint:
        :return:
        """
        attachment = Attachment(content_type=content_type, content_url=url)
        if name:
            attachment.name = name
        return attachment_activity(AttachmentLayoutTypes.list, [attachment], text, speak, input_hint)


import asyncio

from .verify_options import VerifyOptions
from .constants import Constants
from .jwt_token_extractor import JwtTokenExtractor
from .claims_identity import ClaimsIdentity
from .credential_provider import CredentialProvider

class ChannelValidation:
    # This claim is ONLY used in the Channel Validation, and not in the emulator validation
    SERVICE_URL_CLAIM = 'serviceurl'

    #
    # TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot
    #
    TO_BOT_FROM_CHANNEL_TOKEN_VALIDATION_PARAMETERS = VerifyOptions(
        issuer=[Constants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER],
        # Audience validation takes place manually in code.
        audience=None,
        clock_tolerance=5 * 60,
        ignore_expiration=False
    )

    @staticmethod
    async def authenticate_token_service_url(auth_header: str, credentials: CredentialProvider, service_url: str, channel_id: str) -> ClaimsIdentity:
        """ Validate the incoming Auth Header

        Validate the incoming Auth Header as a token sent from the Bot Framework Service.
        A token issued by the Bot Framework emulator will FAIL this check.

        :param auth_header: The raw HTTP header in the format: 'Bearer [longString]'
        :type auth_header: str
        :param credentials: The user defined set of valid credentials, such as the AppId.
        :type credentials: CredentialProvider
        :param service_url: Claim value that must match in the identity.
        :type service_url: str

        :return: A valid ClaimsIdentity.
        :raises Exception:
        """
        identity = await asyncio.ensure_future(
            ChannelValidation.authenticate_token(auth_header, credentials, channel_id))

        service_url_claim = identity.get_claim_value(ChannelValidation.SERVICE_URL_CLAIM)
        if service_url_claim != service_url:
            # Claim must match. Not Authorized.
            raise Exception('Unauthorized. service_url claim do not match.')

        return identity

    @staticmethod
    async def authenticate_token(auth_header: str, credentials: CredentialProvider, channel_id: str) -> ClaimsIdentity:
        """ Validate the incoming Auth Header

        Validate the incoming Auth Header as a token sent from the Bot Framework Service.
        A token issued by the Bot Framework emulator will FAIL this check.

        :param auth_header: The raw HTTP header in the format: 'Bearer [longString]'
        :type auth_header: str
        :param credentials: The user defined set of valid credentials, such as the AppId.
        :type credentials: CredentialProvider

        :return: A valid ClaimsIdentity.
        :raises Exception:
        """
        token_extractor = JwtTokenExtractor(
            ChannelValidation.TO_BOT_FROM_CHANNEL_TOKEN_VALIDATION_PARAMETERS,
            Constants.TO_BOT_FROM_CHANNEL_OPEN_ID_METADATA_URL,
            Constants.ALLOWED_SIGNING_ALGORITHMS)

        identity = await asyncio.ensure_future(
            token_extractor.get_identity_from_auth_header(auth_header, channel_id))
        if not identity:
            # No valid identity. Not Authorized.
            raise Exception('Unauthorized. No valid identity.')

        if not identity.isAuthenticated:
            # The token is in some way invalid. Not Authorized.
            raise Exception('Unauthorized. Is not authenticated')

        # Now check that the AppID in the claimset matches
        # what we're looking for. Note that in a multi-tenant bot, this value
        # comes from developer code that may be reaching out to a service, hence the
        # Async validation.

        # Look for the "aud" claim, but only if issued from the Bot Framework
        if identity.get_claim_value(Constants.ISSUER_CLAIM) != Constants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER:
            # The relevant Audience Claim MUST be present. Not Authorized.
            raise Exception('Unauthorized. Audience Claim MUST be present.')

        # The AppId from the claim in the token must match the AppId specified by the developer.
        # Note that the Bot Framework uses the Audience claim ("aud") to pass the AppID.
        aud_claim = identity.get_claim_value(Constants.AUDIENCE_CLAIM)
        is_valid_app_id = await asyncio.ensure_future(credentials.is_valid_appid(aud_claim or ""))
        if not is_valid_app_id:
            # The AppId is not valid or not present. Not Authorized.
            raise Exception('Unauthorized. Invalid AppId passed on token: ', aud_claim)

        return identity


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

from datetime import datetime

from botbuilder.schema import (
    Activity,
    ActivityTypes,
    ChannelAccount,
    ConversationAccount,
)

class ActivityUtil(object):
    @staticmethod
    def create_trace(
        turn_activity: Activity,
        name: str,
        value: object = None,
        value_type: str = None,
        label: str = None,
    ) -> Activity:
        """Creates a trace activity based on this activity.

        :param turn_activity:
        :type turn_activity: Activity
        :param name: The value to assign to the trace activity's <see cref="Activity.name"/> property.
        :type name: str
        :param value: The value to assign to the trace activity's <see cref="Activity.value"/> property., defaults to None
        :param value: object, optional
        :param value_type: The value to assign to the trace activity's <see cref="Activity.value_type"/> property, defaults to None
        :param value_type: str, optional
        :param label: The value to assign to the trace activity's <see cref="Activity.label"/> property, defaults to None
        :param label: str, optional
        :return: The created trace activity.
        :rtype: Activity
        """

        from_property = (
            ChannelAccount(
                id=turn_activity.recipient.id, name=turn_activity.recipient.name
            )
            if turn_activity.recipient is not None
            else ChannelAccount()
        )
        if value_type is None and value is not None:
            value_type = type(value).__name__

        reply = Activity(
            type=ActivityTypes.trace,
            timestamp=datetime.utcnow(),
            from_property=from_property,
            recipient=ChannelAccount(
                id=turn_activity.from_property.id, name=turn_activity.from_property.name
            ),
            reply_to_id=turn_activity.id,
            service_url=turn_activity.service_url,
            channel_id=turn_activity.channel_id,
            conversation=ConversationAccount(
                is_group=turn_activity.conversation.is_group,
                id=turn_activity.conversation.id,
                name=turn_activity.conversation.name,
            ),
            name=name,
            label=label,
            value_type=value_type,
            value=value,
        )
        return reply


# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import inspect
from .dialog import Dialog
from .dialog_state import DialogState
from .dialog_turn_result import DialogTurnResult
from .dialog_reason import DialogReason
from .dialog_context import DialogContext
from botbuilder.core import (
                            TurnContext,
                            BotAssert,
                            StatePropertyAccessor
                            )
from typing import Dict

class DialogSet():

    def __init__(self, dialog_state: StatePropertyAccessor = None):
        if dialog_state is None:
            frame = inspect.currentframe().f_back
            try:
                # try to access the caller's "self"
                try:
                    self_obj = frame.f_locals['self']
                except KeyError:
                    raise TypeError('DialogSet(): dialog_state cannot be None.')
                # Only ComponentDialog can initialize with None dialog_state
                if not type(self_obj).__name__ is "ComponentDialog":
                    raise TypeError('DialogSet(): dialog_state cannot be None.')
            finally:
                # make sure to clean up the frame at the end to avoid ref cycles
                del frame

        self._dialog_state = dialog_state
        # self.__telemetry_client = NullBotTelemetryClient.Instance;

        self._dialogs: Dict[str, object] = {}

    async def add(self, dialog: Dialog):
        """
        Adds a new dialog to the set and returns the added dialog.
        :param dialog: The dialog to add.
        """
        if dialog is None or not isinstance(dialog, Dialog):
            raise TypeError('DialogSet.add(): dialog cannot be None and must be a Dialog or derived class.')

        if dialog.id in self._dialogs:
            raise TypeError("DialogSet.add(): A dialog with an id of '%s' already added." % dialog.id)

        # dialog.telemetry_client = this._telemetry_client;
        self._dialogs[dialog.id] = dialog

        return self

    async def create_context(self, turn_context: TurnContext) -> DialogContext:
        BotAssert.context_not_null(turn_context)

        if not self._dialog_state:
            raise RuntimeError("DialogSet.CreateContextAsync(): DialogSet created with a null IStatePropertyAccessor.")

        state = await self._dialog_state.get(turn_context, lambda: DialogState())

        return DialogContext(self, turn_context, state)

    async def find(self, dialog_id: str) -> Dialog:
        """
        Finds a dialog that was previously added to the set using add(dialog)
        :param dialog_id: ID of the dialog/prompt to look up.
        :return: The dialog if found, otherwise null.
        """
        if (not dialog_id):
            raise TypeError('DialogContext.find(): dialog_id cannot be None.')

        if dialog_id in self._dialogs:
            return self._dialogs[dialog_id]

        return None

    def __str__(self):
        if len(self._dialogs) <= 0:
            return "dialog set empty!"
        return ' '.join(map(str, self._dialogs.keys()))


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

from .turn_context import TurnContext
from .bot_state import BotState
from .storage import Storage

class UserState(BotState):
    """
    Reads and writes user state for your bot to storage.
    """

    no_key_error_message = 'UserState: channel_id and/or conversation missing from context.activity.'

    def __init__(self, storage: Storage, namespace=''):
        """
        Creates a new UserState instance.
        :param storage:
        :param namespace:
        """
        self.namespace = namespace

        def call_get_storage_key(context):
            key = self.get_storage_key(context)
            if key is None:
                raise AttributeError(self.no_key_error_message)
            else:
                return key

        super(UserState, self).__init__(storage, "UserState")

    def get_storage_key(self, context: TurnContext) -> str:
        """
        Returns the storage key for the current user state.
        :param context:
        :return:
        """
        activity = context.activity
        channel_id = getattr(activity, 'channel_id', None)
        user_id = getattr(activity.from_property, 'id', None) if hasattr(activity, 'from_property') else None

        storage_key = None
        if channel_id and user_id:
            storage_key = "%s/users/%s" % (channel_id, user_id)
        return storage_key


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

from typing import Dict, NamedTuple

from . import IntentScore

class TopIntent(NamedTuple):
    """The top scoring intent and its score."""

    intent: str
    score: float

class RecognizerResult:
    """
    Contains recognition results generated by a recognizer.
    """

    def __init__(
        self,
        text: str = None,
        altered_text: str = None,
        intents: Dict[str, IntentScore] = None,
        entities: Dict[str, object] = None,
    ):
        self._text: str = text
        self._altered_text: str = altered_text
        self._intents: Dict[str, IntentScore] = intents
        self._entities: Dict[str, object] = entities
        self._properties: Dict[str, object] = {}

    @property
    def text(self) -> str:
        """Gets the input text to recognize.

        :return: Original text to recognizer.
        :rtype: str
        """

        return self._text

    @text.setter
    def text(self, value: str) -> None:
        """Sets the input text to recognize.

        :param value: Original text to recognizer.
        :type value: str
        :return:
        :rtype: None
        """

        self._text = value

    @property
    def altered_text(self) -> str:
        """Gets the input text as modified by the recognizer, for example for spelling correction.

        :return: Text modified by recognizer.
        :rtype: str
        """

        return self._altered_text

    @altered_text.setter
    def altered_text(self, value: str) -> None:
        """Sets the input text as modified by the recognizer, for example for spelling correction.

        :param value: Text modified by recognizer.
        :type value: str
        :return:
        :rtype: None
        """

        self._altered_text = value

    @property
    def intents(self) -> Dict[str, IntentScore]:
        """Gets the recognized intents, with the intent as key and the confidence as value.

        :return: Mapping from intent to information about the intent.
        :rtype: Dict[str, IntentScore]
        """

        return self._intents

    @intents.setter
    def intents(self, value: Dict[str, IntentScore]) -> None:
        """Sets the recognized intents, with the intent as key and the confidence as value.

        :param value: Mapping from intent to information about the intent.
        :type value: Dict[str, IntentScore]
        :return:
        :rtype: None
        """

        self._intents = value

    @property
    def entities(self) -> Dict:
        """Gets the recognized top-level entities.

        :return: Object with each top-level recognized entity as a key.
        :rtype: Dict
        """

        return self._entities

    @entities.setter
    def entities(self, value: Dict) -> None:
        """Sets the recognized top-level entities.

        :param value: Object with each top-level recognized entity as a key.
        :type value: Dict
        :return:
        :rtype: None
        """

        self._entities = value

    @property
    def properties(self) -> Dict[str, object]:
        """Gets properties that are not otherwise defined by the <see cref="RecognizerResult"/> type but that
        might appear in the REST JSON object.

        :return: The extended properties for the object.
        :rtype: Dict[str, object]
        """

        return self._properties

    @properties.setter
    def properties(self, value: Dict[str, object]) -> None:
        """Sets properties that are not otherwise defined by the <see cref="RecognizerResult"/> type but that
        might appear in the REST JSON object.

        :param value: The extended properties for the object.
        :type value: Dict[str, object]
        :return:
        :rtype: None
        """

        self._properties = value

    def get_top_scoring_intent(self) -> TopIntent:
        """Return the top scoring intent and its score.

        :return: Intent and score.
        :rtype: TopIntent
        """

        if self.intents is None:
            raise TypeError("result.intents can't be None")

        top_intent = TopIntent(intent="", score=0.0)
        for intent_name, intent_score in self.intents.items():
            score = intent_score.score
            if score > top_intent[1]:
                top_intent = TopIntent(intent_name, score)

        return top_intent