if (string_custom_ids is not None):
        string_custom_ids = tuple(string_custom_ids)

__all__ = ('ComponentCommand', )

try:
    # CPython
    from re import Pattern
except ImportError:
    from re import _pattern_type as Pattern

from functools import partial as partial_func

from ...discord.interaction.components import COMPONENT_CUSTOM_ID_LENGTH_MAX
from ...discord.events.handling_helpers import route_value, Router, create_event_from_class
from .wrappers import SlasherCommandWrapper
from .utils import _check_maybe_route
from .converters import get_component_command_parameter_converters, RegexMatcher, \
    check_component_converters_satisfy_string, check_component_converters_satisfy_regex
from .responding import process_command_coroutine
from .exceptions import handle_command_exception, test_exception_handler, _register_exception_handler

SLASH_COMMAND_PARAMETER_NAMES = ('command', 'custom_id')

SLASH_COMMAND_NAME_NAME = None
SLASH_COMMAND_COMMAND_NAME = 'command'

def _validate_custom_id(custom_id):
    """
    Validates a `custom_id` value.

    Parameters
    ----------
    custom_id : `str` or `re.Pattern`
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `str`or `re.Pattern`
        The validated custom_id.

    Raises
    ------
    ValueError
        If `custom_id`'s length is out of the expected range.
    """
    if isinstance(custom_id, str):
        if type(custom_id) is not str:
            custom_id = str(custom_id)

        custom_id_length = len(custom_id)
        if (custom_id_length < 1) or (custom_id_length > COMPONENT_CUSTOM_ID_LENGTH_MAX):
            raise ValueError(f'`custom_id` length can be in range [1:{COMPONENT_CUSTOM_ID_LENGTH_MAX}], got '
                f'{custom_id_length}; {custom_id!r}.')

    return custom_id

def _validate_custom_ids(custom_id):
    """
    Validates one or more `custom_id` values.

    Parameters
    ----------
    custom_id : `str`, (`list` or `set`) of `str`.
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `set` of (`str` or `re.Pattern`)
        The non-duped custom-ids.

    Raises
    ------
    TypeError
        If `custom_id`'s type is incorrect.
    ValueError
        If a `custom_id`'s length is out of the expected range.
    """
    custom_ids = set()
    if isinstance(custom_id, (str, Pattern)):
        custom_id = _validate_custom_id(custom_id)
        custom_ids.add(custom_id)
    elif isinstance(custom_id, (list, set)):

        for sub_custom_id in custom_id:
            if isinstance(sub_custom_id, (str, Pattern)):
                sub_custom_id = _validate_custom_id(sub_custom_id)
                custom_ids.add(sub_custom_id)
                continue

            raise TypeError(f'`custom_id` contains a non `st` element, got: {custom_id.__class__.__name__}.')

        if not custom_id:
            raise ValueError(f'`custom_id` received as empty {custom_id.__class__.__name__}.')

    else:
        raise TypeError(f'`custom_id` can be given as `str`, (`list`, `set`) of `str`, got '
            f'{custom_id.__class__.__name__}.')

    return custom_ids

def split_and_check_satisfaction(custom_ids, parameter_converters):
    """
    Splits custom id-s to `str` and to `re.Pattern`-s and validates them.

    Parameters
    ----------
    custom_ids : `set` of (`str` or `re.Pattern`)
        The custom-ids to split and validate.
    parameter_converters : `tuple` of ``ParameterConverter``
        The parameter converters generated from a component command.

    Returns
    -------
    string_custom_ids : `None` or `tuple` of `str`
        String custom ids.
    regex_custom_ids : `None` or `tuple` of ``RegexMatcher``
        Regex custom ids.

    Raises
    ------
    ValueError
        A string or regex pattern is not satisfied.
    """
    # Build
    string_custom_ids = None
    regex_custom_ids = None
    for custom_id in custom_ids:
        if isinstance(custom_id, str):
            if string_custom_ids is None:
                string_custom_ids = []

            string_custom_ids.append(custom_id)
        else:
            if regex_custom_ids is None:
                regex_custom_ids = []

            regex_custom_ids.append(custom_id)

    # Convert

    if (regex_custom_ids is not None):
        regex_custom_ids = tuple(RegexMatcher(regex_custom_id) for regex_custom_id in regex_custom_ids)

    # Check
    if (string_custom_ids is not None):
        check_component_converters_satisfy_string(parameter_converters)

    if (regex_custom_ids is not None):
        for regex_custom_id in regex_custom_ids:
            check_component_converters_satisfy_regex(parameter_converters, regex_custom_id)

    # Good
    return string_custom_ids, regex_custom_ids

class ComponentCommand:
    """
    A command, which is called if a command interaction is executed with a specific `custom_id`.

    Attributes
    ----------
    _command_function : `async-callable˛
        The command's function to call.
    _exception_handlers : `None` or `list` of `CoroutineFunction`
        Exception handlers added with ``.error`` to the interaction handler.

        Same as ``Slasher._exception_handlers``.

    _parent_reference : `None` or ``WeakReferer`` to ``SlasherApplicationCommand``
        The parent slasher of the component command.
    _parameter_converters : `tuple` of ``ParameterConverter``
        Parsers to parse command parameters.
    _string_custom_ids : `None` or `tuple` of `str`
        The custom id-s to wait for.
    _regex_custom_ids : `None` or `tuple` of `re.Pattern`.
        Regex pattern to match custom-ids.
    """
    __slots__ = ('_command_function', '_exception_handlers', '_parent_reference', '_parameter_converters',
        '_regex_custom_ids', '_string_custom_ids')

    @classmethod
    def from_class(cls, klass):
        """
        Creates a new ``ComponentCommand`` instance from the given `klass`.

        Parameters
        ----------
        klass : `type`
            The class to create component command from.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            If any attribute's type is incorrect.
        ValueError
            If any attribute's value is incorrect.
        """
        return create_event_from_class(cls, klass, SLASH_COMMAND_PARAMETER_NAMES, SLASH_COMMAND_NAME_NAME,
            SLASH_COMMAND_COMMAND_NAME)

    def __new__(cls, func, custom_id):
        """
        Creates a new ``ComponentCommand`` instance with the given parameters

        Parameters
        ----------
        func : `None` or `async-callable`, Optional
            The function used as the command when using the respective slash command.
        custom_id : `str`, (`list` or `set`) of `str`, `tuple` of (`str`, (`list` or `set`) of `str`)
            Custom id to match by the component command.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            - If `func` is not async callable, neither cannot be instanced to async.
            - If `func` accepts keyword only parameters.
            - If `func` accepts `*args`.
            - If `func` accepts `**kwargs`
            - If `custom_id`'s type is incorrect.
        ValueError:
            - If no `custom_id` was received.
            - If `custom_id` contains incorrect value.
        """
        if (func is not None) and isinstance(func, SlasherCommandWrapper):
            command, wrappers = func.fetch_function_and_wrappers_back()
        else:
            command = func
            wrappers = None

        route_to = 0
        custom_id, route_to = _check_maybe_route('name', custom_id, route_to, _validate_custom_ids)

        command, parameter_converters = get_component_command_parameter_converters(command)

        if route_to:
            custom_id = route_value(custom_id, route_to)

            router = []

            for custom_id in custom_id:
                string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

                self = object.__new__(cls)
                self._command_function = command
                self._parameter_converters = parameter_converters
                self._string_custom_ids = string_custom_ids
                self._regex_custom_ids = regex_custom_ids
                self._parent_reference = None
                self._exception_handlers = None

                if (wrappers is not None):
                    for wrapper in wrappers:
                        wrapper.apply(self)

                router.append(self)

            return Router(router)
        else:
            string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

            self = object.__new__(cls)
            self._command_function = command
            self._parameter_converters = parameter_converters
            self._string_custom_ids = string_custom_ids
            self._regex_custom_ids = regex_custom_ids
            self._parent_reference = None
            self._exception_handlers = None

            if (wrappers is not None):
                for wrapper in wrappers:
                    wrapper.apply(self)

            return self

    def __repr__(self):
        """Returns the component command's representation."""
        repr_parts = ['<', self.__class__.__name__]

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is None):
            field_added = False
        else:
            field_added = True

            repr_parts.append(' string_custom_ids=[')
            index = 0
            limit = len(string_custom_ids)

            while True:
                string_custom_id = string_custom_ids[index]
                repr_parts.append(repr(string_custom_id))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            if field_added:
                repr_parts.append(', ')

            repr_parts.append(' regex_custom_ids=[')
            index = 0
            limit = len(regex_custom_ids)

            while True:
                regex_custom_id = regex_custom_ids[index]
                repr_parts.append(repr(regex_custom_id.pattern.pattern))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        return ''.join(repr_parts)

    async def __call__(self, client, interaction_event, regex_match):
        """
        Calls the component command.

        This method is a coroutine.

        Parameters
        ----------
        client : ``Client``
            The respective client who received the event.
        interaction_event : ``InteractionEvent``
            The received interaction event.
        regex_match : `None` or ``RegexMatch``
            The matched regex if applicable.
        """
        parameters = []

        for parameter_converter in self._parameter_converters:
            try:
                parameter = await parameter_converter(client, interaction_event, regex_match)
            except BaseException as err:
                exception = err
            else:
                parameters.append(parameter)
                continue

            await handle_command_exception(
                self,
                client,
                interaction_event,
                exception,
            )
            return

        command_coroutine = self._command_function(*parameters)

        try:
            await process_command_coroutine(client, interaction_event, False, command_coroutine)
        except BaseException as err:
            exception = err
        else:
            return

        await handle_command_exception(
            self,
            client,
            interaction_event,
            exception,
        )
        return

    def __hash__(self):
        """Returns the component command's hash value."""
        hash_value = 0

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is not None):
            hash_value ^= hash(string_custom_ids)

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            hash_value ^= hash(regex_custom_ids)

        command_function = self._command_function
        try:
            command_hash_value = hash(command_function)
        except KeyError:
            command_hash_value = object.__hash__(command_function)

        hash_value ^= command_hash_value
        return hash_value

    def __eq__(self, other):
        """Returns whether self equals to other."""
        if type(self) is not type(other):
            return NotImplemented

        if self._command_function != other._command_function:
            return False

        if self._string_custom_ids != other._string_custom_ids:
            return False

        if self._regex_custom_ids != other._regex_custom_ids:
            return False

        if self._exception_handlers != other._exception_handlers:
            return False

        return True

    def copy(self):
        """
        Copies the component command.

        Returns
        -------
        new : ``ComponentCommand``
        """
        new = object.__new__(type(self))
        new._command_function = self._command_function
        new._parameter_converters = self._parameter_converters
        new._string_custom_ids = self._string_custom_ids
        new._regex_custom_ids = self._regex_custom_ids
        new._parent_reference = None

        exception_handlers = self._exception_handlers
        if (exception_handlers is not None):
            exception_handlers = exception_handlers.copy()
        new._exception_handlers = exception_handlers

        return new

    def error(self, exception_handler=None, *, first=False):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `None` or `CoroutineFunction`, Optional
            Exception handler to register.
        first : `bool`, Optional (Keyword Only)
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler / wrapper : `CoroutineFunction` / `functools.partial`
            If `exception_handler` is not given, returns a wrapper.
        """
        if exception_handler is None:
            return partial_func(_register_exception_handler, first)

        return self._register_exception_handler(exception_handler, first)

    def _register_exception_handler(self, exception_handler, first):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `CoroutineFunction`
            Exception handler to register.
        first : `bool`
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler : `CoroutineFunction`
        """
        test_exception_handler(exception_handler)

        exception_handlers = self._exception_handlers
        if exception_handlers is None:
            self._exception_handlers = exception_handlers = []

        if first:
            exception_handlers.insert(0, exception_handler)
        else:
            exception_handlers.append(exception_handler)

        return exception_handler


for sub_custom_id in custom_id:
            if isinstance(sub_custom_id, (str, Pattern)):
                sub_custom_id = _validate_custom_id(sub_custom_id)
                custom_ids.add(sub_custom_id)
                continue

            raise TypeError(f'`custom_id` contains a non `st` element, got: {custom_id.__class__.__name__}.')

__all__ = ('ComponentCommand', )

try:
    # CPython
    from re import Pattern
except ImportError:
    from re import _pattern_type as Pattern

from functools import partial as partial_func

from ...discord.interaction.components import COMPONENT_CUSTOM_ID_LENGTH_MAX
from ...discord.events.handling_helpers import route_value, Router, create_event_from_class
from .wrappers import SlasherCommandWrapper
from .utils import _check_maybe_route
from .converters import get_component_command_parameter_converters, RegexMatcher, \
    check_component_converters_satisfy_string, check_component_converters_satisfy_regex
from .responding import process_command_coroutine
from .exceptions import handle_command_exception, test_exception_handler, _register_exception_handler

SLASH_COMMAND_PARAMETER_NAMES = ('command', 'custom_id')

SLASH_COMMAND_NAME_NAME = None
SLASH_COMMAND_COMMAND_NAME = 'command'

def _validate_custom_id(custom_id):
    """
    Validates a `custom_id` value.

    Parameters
    ----------
    custom_id : `str` or `re.Pattern`
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `str`or `re.Pattern`
        The validated custom_id.

    Raises
    ------
    ValueError
        If `custom_id`'s length is out of the expected range.
    """
    if isinstance(custom_id, str):
        if type(custom_id) is not str:
            custom_id = str(custom_id)

        custom_id_length = len(custom_id)
        if (custom_id_length < 1) or (custom_id_length > COMPONENT_CUSTOM_ID_LENGTH_MAX):
            raise ValueError(f'`custom_id` length can be in range [1:{COMPONENT_CUSTOM_ID_LENGTH_MAX}], got '
                f'{custom_id_length}; {custom_id!r}.')

    return custom_id

def _validate_custom_ids(custom_id):
    """
    Validates one or more `custom_id` values.

    Parameters
    ----------
    custom_id : `str`, (`list` or `set`) of `str`.
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `set` of (`str` or `re.Pattern`)
        The non-duped custom-ids.

    Raises
    ------
    TypeError
        If `custom_id`'s type is incorrect.
    ValueError
        If a `custom_id`'s length is out of the expected range.
    """
    custom_ids = set()
    if isinstance(custom_id, (str, Pattern)):
        custom_id = _validate_custom_id(custom_id)
        custom_ids.add(custom_id)
    elif isinstance(custom_id, (list, set)):

        if not custom_id:
            raise ValueError(f'`custom_id` received as empty {custom_id.__class__.__name__}.')

    else:
        raise TypeError(f'`custom_id` can be given as `str`, (`list`, `set`) of `str`, got '
            f'{custom_id.__class__.__name__}.')

    return custom_ids

def split_and_check_satisfaction(custom_ids, parameter_converters):
    """
    Splits custom id-s to `str` and to `re.Pattern`-s and validates them.

    Parameters
    ----------
    custom_ids : `set` of (`str` or `re.Pattern`)
        The custom-ids to split and validate.
    parameter_converters : `tuple` of ``ParameterConverter``
        The parameter converters generated from a component command.

    Returns
    -------
    string_custom_ids : `None` or `tuple` of `str`
        String custom ids.
    regex_custom_ids : `None` or `tuple` of ``RegexMatcher``
        Regex custom ids.

    Raises
    ------
    ValueError
        A string or regex pattern is not satisfied.
    """
    # Build
    string_custom_ids = None
    regex_custom_ids = None
    for custom_id in custom_ids:
        if isinstance(custom_id, str):
            if string_custom_ids is None:
                string_custom_ids = []

            string_custom_ids.append(custom_id)
        else:
            if regex_custom_ids is None:
                regex_custom_ids = []

            regex_custom_ids.append(custom_id)

    # Convert
    if (string_custom_ids is not None):
        string_custom_ids = tuple(string_custom_ids)

    if (regex_custom_ids is not None):
        regex_custom_ids = tuple(RegexMatcher(regex_custom_id) for regex_custom_id in regex_custom_ids)

    # Check
    if (string_custom_ids is not None):
        check_component_converters_satisfy_string(parameter_converters)

    if (regex_custom_ids is not None):
        for regex_custom_id in regex_custom_ids:
            check_component_converters_satisfy_regex(parameter_converters, regex_custom_id)

    # Good
    return string_custom_ids, regex_custom_ids

class ComponentCommand:
    """
    A command, which is called if a command interaction is executed with a specific `custom_id`.

    Attributes
    ----------
    _command_function : `async-callable˛
        The command's function to call.
    _exception_handlers : `None` or `list` of `CoroutineFunction`
        Exception handlers added with ``.error`` to the interaction handler.

        Same as ``Slasher._exception_handlers``.

    _parent_reference : `None` or ``WeakReferer`` to ``SlasherApplicationCommand``
        The parent slasher of the component command.
    _parameter_converters : `tuple` of ``ParameterConverter``
        Parsers to parse command parameters.
    _string_custom_ids : `None` or `tuple` of `str`
        The custom id-s to wait for.
    _regex_custom_ids : `None` or `tuple` of `re.Pattern`.
        Regex pattern to match custom-ids.
    """
    __slots__ = ('_command_function', '_exception_handlers', '_parent_reference', '_parameter_converters',
        '_regex_custom_ids', '_string_custom_ids')

    @classmethod
    def from_class(cls, klass):
        """
        Creates a new ``ComponentCommand`` instance from the given `klass`.

        Parameters
        ----------
        klass : `type`
            The class to create component command from.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            If any attribute's type is incorrect.
        ValueError
            If any attribute's value is incorrect.
        """
        return create_event_from_class(cls, klass, SLASH_COMMAND_PARAMETER_NAMES, SLASH_COMMAND_NAME_NAME,
            SLASH_COMMAND_COMMAND_NAME)

    def __new__(cls, func, custom_id):
        """
        Creates a new ``ComponentCommand`` instance with the given parameters

        Parameters
        ----------
        func : `None` or `async-callable`, Optional
            The function used as the command when using the respective slash command.
        custom_id : `str`, (`list` or `set`) of `str`, `tuple` of (`str`, (`list` or `set`) of `str`)
            Custom id to match by the component command.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            - If `func` is not async callable, neither cannot be instanced to async.
            - If `func` accepts keyword only parameters.
            - If `func` accepts `*args`.
            - If `func` accepts `**kwargs`
            - If `custom_id`'s type is incorrect.
        ValueError:
            - If no `custom_id` was received.
            - If `custom_id` contains incorrect value.
        """
        if (func is not None) and isinstance(func, SlasherCommandWrapper):
            command, wrappers = func.fetch_function_and_wrappers_back()
        else:
            command = func
            wrappers = None

        route_to = 0
        custom_id, route_to = _check_maybe_route('name', custom_id, route_to, _validate_custom_ids)

        command, parameter_converters = get_component_command_parameter_converters(command)

        if route_to:
            custom_id = route_value(custom_id, route_to)

            router = []

            for custom_id in custom_id:
                string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

                self = object.__new__(cls)
                self._command_function = command
                self._parameter_converters = parameter_converters
                self._string_custom_ids = string_custom_ids
                self._regex_custom_ids = regex_custom_ids
                self._parent_reference = None
                self._exception_handlers = None

                if (wrappers is not None):
                    for wrapper in wrappers:
                        wrapper.apply(self)

                router.append(self)

            return Router(router)
        else:
            string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

            self = object.__new__(cls)
            self._command_function = command
            self._parameter_converters = parameter_converters
            self._string_custom_ids = string_custom_ids
            self._regex_custom_ids = regex_custom_ids
            self._parent_reference = None
            self._exception_handlers = None

            if (wrappers is not None):
                for wrapper in wrappers:
                    wrapper.apply(self)

            return self

    def __repr__(self):
        """Returns the component command's representation."""
        repr_parts = ['<', self.__class__.__name__]

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is None):
            field_added = False
        else:
            field_added = True

            repr_parts.append(' string_custom_ids=[')
            index = 0
            limit = len(string_custom_ids)

            while True:
                string_custom_id = string_custom_ids[index]
                repr_parts.append(repr(string_custom_id))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            if field_added:
                repr_parts.append(', ')

            repr_parts.append(' regex_custom_ids=[')
            index = 0
            limit = len(regex_custom_ids)

            while True:
                regex_custom_id = regex_custom_ids[index]
                repr_parts.append(repr(regex_custom_id.pattern.pattern))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        return ''.join(repr_parts)

    async def __call__(self, client, interaction_event, regex_match):
        """
        Calls the component command.

        This method is a coroutine.

        Parameters
        ----------
        client : ``Client``
            The respective client who received the event.
        interaction_event : ``InteractionEvent``
            The received interaction event.
        regex_match : `None` or ``RegexMatch``
            The matched regex if applicable.
        """
        parameters = []

        for parameter_converter in self._parameter_converters:
            try:
                parameter = await parameter_converter(client, interaction_event, regex_match)
            except BaseException as err:
                exception = err
            else:
                parameters.append(parameter)
                continue

            await handle_command_exception(
                self,
                client,
                interaction_event,
                exception,
            )
            return

        command_coroutine = self._command_function(*parameters)

        try:
            await process_command_coroutine(client, interaction_event, False, command_coroutine)
        except BaseException as err:
            exception = err
        else:
            return

        await handle_command_exception(
            self,
            client,
            interaction_event,
            exception,
        )
        return

    def __hash__(self):
        """Returns the component command's hash value."""
        hash_value = 0

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is not None):
            hash_value ^= hash(string_custom_ids)

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            hash_value ^= hash(regex_custom_ids)

        command_function = self._command_function
        try:
            command_hash_value = hash(command_function)
        except KeyError:
            command_hash_value = object.__hash__(command_function)

        hash_value ^= command_hash_value
        return hash_value

    def __eq__(self, other):
        """Returns whether self equals to other."""
        if type(self) is not type(other):
            return NotImplemented

        if self._command_function != other._command_function:
            return False

        if self._string_custom_ids != other._string_custom_ids:
            return False

        if self._regex_custom_ids != other._regex_custom_ids:
            return False

        if self._exception_handlers != other._exception_handlers:
            return False

        return True

    def copy(self):
        """
        Copies the component command.

        Returns
        -------
        new : ``ComponentCommand``
        """
        new = object.__new__(type(self))
        new._command_function = self._command_function
        new._parameter_converters = self._parameter_converters
        new._string_custom_ids = self._string_custom_ids
        new._regex_custom_ids = self._regex_custom_ids
        new._parent_reference = None

        exception_handlers = self._exception_handlers
        if (exception_handlers is not None):
            exception_handlers = exception_handlers.copy()
        new._exception_handlers = exception_handlers

        return new

    def error(self, exception_handler=None, *, first=False):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `None` or `CoroutineFunction`, Optional
            Exception handler to register.
        first : `bool`, Optional (Keyword Only)
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler / wrapper : `CoroutineFunction` / `functools.partial`
            If `exception_handler` is not given, returns a wrapper.
        """
        if exception_handler is None:
            return partial_func(_register_exception_handler, first)

        return self._register_exception_handler(exception_handler, first)

    def _register_exception_handler(self, exception_handler, first):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `CoroutineFunction`
            Exception handler to register.
        first : `bool`
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler : `CoroutineFunction`
        """
        test_exception_handler(exception_handler)

        exception_handlers = self._exception_handlers
        if exception_handlers is None:
            self._exception_handlers = exception_handlers = []

        if first:
            exception_handlers.insert(0, exception_handler)
        else:
            exception_handlers.append(exception_handler)

        return exception_handler


while True:
                string_custom_id = string_custom_ids[index]
                repr_parts.append(repr(string_custom_id))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

__all__ = ('ComponentCommand', )

try:
    # CPython
    from re import Pattern
except ImportError:
    from re import _pattern_type as Pattern

from functools import partial as partial_func

from ...discord.interaction.components import COMPONENT_CUSTOM_ID_LENGTH_MAX
from ...discord.events.handling_helpers import route_value, Router, create_event_from_class
from .wrappers import SlasherCommandWrapper
from .utils import _check_maybe_route
from .converters import get_component_command_parameter_converters, RegexMatcher, \
    check_component_converters_satisfy_string, check_component_converters_satisfy_regex
from .responding import process_command_coroutine
from .exceptions import handle_command_exception, test_exception_handler, _register_exception_handler

SLASH_COMMAND_PARAMETER_NAMES = ('command', 'custom_id')

SLASH_COMMAND_NAME_NAME = None
SLASH_COMMAND_COMMAND_NAME = 'command'

def _validate_custom_id(custom_id):
    """
    Validates a `custom_id` value.

    Parameters
    ----------
    custom_id : `str` or `re.Pattern`
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `str`or `re.Pattern`
        The validated custom_id.

    Raises
    ------
    ValueError
        If `custom_id`'s length is out of the expected range.
    """
    if isinstance(custom_id, str):
        if type(custom_id) is not str:
            custom_id = str(custom_id)

        custom_id_length = len(custom_id)
        if (custom_id_length < 1) or (custom_id_length > COMPONENT_CUSTOM_ID_LENGTH_MAX):
            raise ValueError(f'`custom_id` length can be in range [1:{COMPONENT_CUSTOM_ID_LENGTH_MAX}], got '
                f'{custom_id_length}; {custom_id!r}.')

    return custom_id

def _validate_custom_ids(custom_id):
    """
    Validates one or more `custom_id` values.

    Parameters
    ----------
    custom_id : `str`, (`list` or `set`) of `str`.
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `set` of (`str` or `re.Pattern`)
        The non-duped custom-ids.

    Raises
    ------
    TypeError
        If `custom_id`'s type is incorrect.
    ValueError
        If a `custom_id`'s length is out of the expected range.
    """
    custom_ids = set()
    if isinstance(custom_id, (str, Pattern)):
        custom_id = _validate_custom_id(custom_id)
        custom_ids.add(custom_id)
    elif isinstance(custom_id, (list, set)):

        for sub_custom_id in custom_id:
            if isinstance(sub_custom_id, (str, Pattern)):
                sub_custom_id = _validate_custom_id(sub_custom_id)
                custom_ids.add(sub_custom_id)
                continue

            raise TypeError(f'`custom_id` contains a non `st` element, got: {custom_id.__class__.__name__}.')

        if not custom_id:
            raise ValueError(f'`custom_id` received as empty {custom_id.__class__.__name__}.')

    else:
        raise TypeError(f'`custom_id` can be given as `str`, (`list`, `set`) of `str`, got '
            f'{custom_id.__class__.__name__}.')

    return custom_ids

def split_and_check_satisfaction(custom_ids, parameter_converters):
    """
    Splits custom id-s to `str` and to `re.Pattern`-s and validates them.

    Parameters
    ----------
    custom_ids : `set` of (`str` or `re.Pattern`)
        The custom-ids to split and validate.
    parameter_converters : `tuple` of ``ParameterConverter``
        The parameter converters generated from a component command.

    Returns
    -------
    string_custom_ids : `None` or `tuple` of `str`
        String custom ids.
    regex_custom_ids : `None` or `tuple` of ``RegexMatcher``
        Regex custom ids.

    Raises
    ------
    ValueError
        A string or regex pattern is not satisfied.
    """
    # Build
    string_custom_ids = None
    regex_custom_ids = None
    for custom_id in custom_ids:
        if isinstance(custom_id, str):
            if string_custom_ids is None:
                string_custom_ids = []

            string_custom_ids.append(custom_id)
        else:
            if regex_custom_ids is None:
                regex_custom_ids = []

            regex_custom_ids.append(custom_id)

    # Convert
    if (string_custom_ids is not None):
        string_custom_ids = tuple(string_custom_ids)

    if (regex_custom_ids is not None):
        regex_custom_ids = tuple(RegexMatcher(regex_custom_id) for regex_custom_id in regex_custom_ids)

    # Check
    if (string_custom_ids is not None):
        check_component_converters_satisfy_string(parameter_converters)

    if (regex_custom_ids is not None):
        for regex_custom_id in regex_custom_ids:
            check_component_converters_satisfy_regex(parameter_converters, regex_custom_id)

    # Good
    return string_custom_ids, regex_custom_ids

class ComponentCommand:
    """
    A command, which is called if a command interaction is executed with a specific `custom_id`.

    Attributes
    ----------
    _command_function : `async-callable˛
        The command's function to call.
    _exception_handlers : `None` or `list` of `CoroutineFunction`
        Exception handlers added with ``.error`` to the interaction handler.

        Same as ``Slasher._exception_handlers``.

    _parent_reference : `None` or ``WeakReferer`` to ``SlasherApplicationCommand``
        The parent slasher of the component command.
    _parameter_converters : `tuple` of ``ParameterConverter``
        Parsers to parse command parameters.
    _string_custom_ids : `None` or `tuple` of `str`
        The custom id-s to wait for.
    _regex_custom_ids : `None` or `tuple` of `re.Pattern`.
        Regex pattern to match custom-ids.
    """
    __slots__ = ('_command_function', '_exception_handlers', '_parent_reference', '_parameter_converters',
        '_regex_custom_ids', '_string_custom_ids')

    @classmethod
    def from_class(cls, klass):
        """
        Creates a new ``ComponentCommand`` instance from the given `klass`.

        Parameters
        ----------
        klass : `type`
            The class to create component command from.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            If any attribute's type is incorrect.
        ValueError
            If any attribute's value is incorrect.
        """
        return create_event_from_class(cls, klass, SLASH_COMMAND_PARAMETER_NAMES, SLASH_COMMAND_NAME_NAME,
            SLASH_COMMAND_COMMAND_NAME)

    def __new__(cls, func, custom_id):
        """
        Creates a new ``ComponentCommand`` instance with the given parameters

        Parameters
        ----------
        func : `None` or `async-callable`, Optional
            The function used as the command when using the respective slash command.
        custom_id : `str`, (`list` or `set`) of `str`, `tuple` of (`str`, (`list` or `set`) of `str`)
            Custom id to match by the component command.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            - If `func` is not async callable, neither cannot be instanced to async.
            - If `func` accepts keyword only parameters.
            - If `func` accepts `*args`.
            - If `func` accepts `**kwargs`
            - If `custom_id`'s type is incorrect.
        ValueError:
            - If no `custom_id` was received.
            - If `custom_id` contains incorrect value.
        """
        if (func is not None) and isinstance(func, SlasherCommandWrapper):
            command, wrappers = func.fetch_function_and_wrappers_back()
        else:
            command = func
            wrappers = None

        route_to = 0
        custom_id, route_to = _check_maybe_route('name', custom_id, route_to, _validate_custom_ids)

        command, parameter_converters = get_component_command_parameter_converters(command)

        if route_to:
            custom_id = route_value(custom_id, route_to)

            router = []

            for custom_id in custom_id:
                string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

                self = object.__new__(cls)
                self._command_function = command
                self._parameter_converters = parameter_converters
                self._string_custom_ids = string_custom_ids
                self._regex_custom_ids = regex_custom_ids
                self._parent_reference = None
                self._exception_handlers = None

                if (wrappers is not None):
                    for wrapper in wrappers:
                        wrapper.apply(self)

                router.append(self)

            return Router(router)
        else:
            string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

            self = object.__new__(cls)
            self._command_function = command
            self._parameter_converters = parameter_converters
            self._string_custom_ids = string_custom_ids
            self._regex_custom_ids = regex_custom_ids
            self._parent_reference = None
            self._exception_handlers = None

            if (wrappers is not None):
                for wrapper in wrappers:
                    wrapper.apply(self)

            return self

    def __repr__(self):
        """Returns the component command's representation."""
        repr_parts = ['<', self.__class__.__name__]

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is None):
            field_added = False
        else:
            field_added = True

            repr_parts.append(' string_custom_ids=[')
            index = 0
            limit = len(string_custom_ids)

            repr_parts.append(']')

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            if field_added:
                repr_parts.append(', ')

            repr_parts.append(' regex_custom_ids=[')
            index = 0
            limit = len(regex_custom_ids)

            while True:
                regex_custom_id = regex_custom_ids[index]
                repr_parts.append(repr(regex_custom_id.pattern.pattern))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        return ''.join(repr_parts)

    async def __call__(self, client, interaction_event, regex_match):
        """
        Calls the component command.

        This method is a coroutine.

        Parameters
        ----------
        client : ``Client``
            The respective client who received the event.
        interaction_event : ``InteractionEvent``
            The received interaction event.
        regex_match : `None` or ``RegexMatch``
            The matched regex if applicable.
        """
        parameters = []

        for parameter_converter in self._parameter_converters:
            try:
                parameter = await parameter_converter(client, interaction_event, regex_match)
            except BaseException as err:
                exception = err
            else:
                parameters.append(parameter)
                continue

            await handle_command_exception(
                self,
                client,
                interaction_event,
                exception,
            )
            return

        command_coroutine = self._command_function(*parameters)

        try:
            await process_command_coroutine(client, interaction_event, False, command_coroutine)
        except BaseException as err:
            exception = err
        else:
            return

        await handle_command_exception(
            self,
            client,
            interaction_event,
            exception,
        )
        return

    def __hash__(self):
        """Returns the component command's hash value."""
        hash_value = 0

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is not None):
            hash_value ^= hash(string_custom_ids)

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            hash_value ^= hash(regex_custom_ids)

        command_function = self._command_function
        try:
            command_hash_value = hash(command_function)
        except KeyError:
            command_hash_value = object.__hash__(command_function)

        hash_value ^= command_hash_value
        return hash_value

    def __eq__(self, other):
        """Returns whether self equals to other."""
        if type(self) is not type(other):
            return NotImplemented

        if self._command_function != other._command_function:
            return False

        if self._string_custom_ids != other._string_custom_ids:
            return False

        if self._regex_custom_ids != other._regex_custom_ids:
            return False

        if self._exception_handlers != other._exception_handlers:
            return False

        return True

    def copy(self):
        """
        Copies the component command.

        Returns
        -------
        new : ``ComponentCommand``
        """
        new = object.__new__(type(self))
        new._command_function = self._command_function
        new._parameter_converters = self._parameter_converters
        new._string_custom_ids = self._string_custom_ids
        new._regex_custom_ids = self._regex_custom_ids
        new._parent_reference = None

        exception_handlers = self._exception_handlers
        if (exception_handlers is not None):
            exception_handlers = exception_handlers.copy()
        new._exception_handlers = exception_handlers

        return new

    def error(self, exception_handler=None, *, first=False):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `None` or `CoroutineFunction`, Optional
            Exception handler to register.
        first : `bool`, Optional (Keyword Only)
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler / wrapper : `CoroutineFunction` / `functools.partial`
            If `exception_handler` is not given, returns a wrapper.
        """
        if exception_handler is None:
            return partial_func(_register_exception_handler, first)

        return self._register_exception_handler(exception_handler, first)

    def _register_exception_handler(self, exception_handler, first):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `CoroutineFunction`
            Exception handler to register.
        first : `bool`
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler : `CoroutineFunction`
        """
        test_exception_handler(exception_handler)

        exception_handlers = self._exception_handlers
        if exception_handlers is None:
            self._exception_handlers = exception_handlers = []

        if first:
            exception_handlers.insert(0, exception_handler)
        else:
            exception_handlers.append(exception_handler)

        return exception_handler


try:
            await process_command_coroutine(client, interaction_event, False, command_coroutine)
        except BaseException as err:
            exception = err
        else:
            return

__all__ = ('ComponentCommand', )

try:
    # CPython
    from re import Pattern
except ImportError:
    from re import _pattern_type as Pattern

from functools import partial as partial_func

from ...discord.interaction.components import COMPONENT_CUSTOM_ID_LENGTH_MAX
from ...discord.events.handling_helpers import route_value, Router, create_event_from_class
from .wrappers import SlasherCommandWrapper
from .utils import _check_maybe_route
from .converters import get_component_command_parameter_converters, RegexMatcher, \
    check_component_converters_satisfy_string, check_component_converters_satisfy_regex
from .responding import process_command_coroutine
from .exceptions import handle_command_exception, test_exception_handler, _register_exception_handler

SLASH_COMMAND_PARAMETER_NAMES = ('command', 'custom_id')

SLASH_COMMAND_NAME_NAME = None
SLASH_COMMAND_COMMAND_NAME = 'command'

def _validate_custom_id(custom_id):
    """
    Validates a `custom_id` value.

    Parameters
    ----------
    custom_id : `str` or `re.Pattern`
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `str`or `re.Pattern`
        The validated custom_id.

    Raises
    ------
    ValueError
        If `custom_id`'s length is out of the expected range.
    """
    if isinstance(custom_id, str):
        if type(custom_id) is not str:
            custom_id = str(custom_id)

        custom_id_length = len(custom_id)
        if (custom_id_length < 1) or (custom_id_length > COMPONENT_CUSTOM_ID_LENGTH_MAX):
            raise ValueError(f'`custom_id` length can be in range [1:{COMPONENT_CUSTOM_ID_LENGTH_MAX}], got '
                f'{custom_id_length}; {custom_id!r}.')

    return custom_id

def _validate_custom_ids(custom_id):
    """
    Validates one or more `custom_id` values.

    Parameters
    ----------
    custom_id : `str`, (`list` or `set`) of `str`.
        The `custom_id` to validate.

    Returns
    -------
    custom_id : `set` of (`str` or `re.Pattern`)
        The non-duped custom-ids.

    Raises
    ------
    TypeError
        If `custom_id`'s type is incorrect.
    ValueError
        If a `custom_id`'s length is out of the expected range.
    """
    custom_ids = set()
    if isinstance(custom_id, (str, Pattern)):
        custom_id = _validate_custom_id(custom_id)
        custom_ids.add(custom_id)
    elif isinstance(custom_id, (list, set)):

        for sub_custom_id in custom_id:
            if isinstance(sub_custom_id, (str, Pattern)):
                sub_custom_id = _validate_custom_id(sub_custom_id)
                custom_ids.add(sub_custom_id)
                continue

            raise TypeError(f'`custom_id` contains a non `st` element, got: {custom_id.__class__.__name__}.')

        if not custom_id:
            raise ValueError(f'`custom_id` received as empty {custom_id.__class__.__name__}.')

    else:
        raise TypeError(f'`custom_id` can be given as `str`, (`list`, `set`) of `str`, got '
            f'{custom_id.__class__.__name__}.')

    return custom_ids

def split_and_check_satisfaction(custom_ids, parameter_converters):
    """
    Splits custom id-s to `str` and to `re.Pattern`-s and validates them.

    Parameters
    ----------
    custom_ids : `set` of (`str` or `re.Pattern`)
        The custom-ids to split and validate.
    parameter_converters : `tuple` of ``ParameterConverter``
        The parameter converters generated from a component command.

    Returns
    -------
    string_custom_ids : `None` or `tuple` of `str`
        String custom ids.
    regex_custom_ids : `None` or `tuple` of ``RegexMatcher``
        Regex custom ids.

    Raises
    ------
    ValueError
        A string or regex pattern is not satisfied.
    """
    # Build
    string_custom_ids = None
    regex_custom_ids = None
    for custom_id in custom_ids:
        if isinstance(custom_id, str):
            if string_custom_ids is None:
                string_custom_ids = []

            string_custom_ids.append(custom_id)
        else:
            if regex_custom_ids is None:
                regex_custom_ids = []

            regex_custom_ids.append(custom_id)

    # Convert
    if (string_custom_ids is not None):
        string_custom_ids = tuple(string_custom_ids)

    if (regex_custom_ids is not None):
        regex_custom_ids = tuple(RegexMatcher(regex_custom_id) for regex_custom_id in regex_custom_ids)

    # Check
    if (string_custom_ids is not None):
        check_component_converters_satisfy_string(parameter_converters)

    if (regex_custom_ids is not None):
        for regex_custom_id in regex_custom_ids:
            check_component_converters_satisfy_regex(parameter_converters, regex_custom_id)

    # Good
    return string_custom_ids, regex_custom_ids

class ComponentCommand:
    """
    A command, which is called if a command interaction is executed with a specific `custom_id`.

    Attributes
    ----------
    _command_function : `async-callable˛
        The command's function to call.
    _exception_handlers : `None` or `list` of `CoroutineFunction`
        Exception handlers added with ``.error`` to the interaction handler.

        Same as ``Slasher._exception_handlers``.

    _parent_reference : `None` or ``WeakReferer`` to ``SlasherApplicationCommand``
        The parent slasher of the component command.
    _parameter_converters : `tuple` of ``ParameterConverter``
        Parsers to parse command parameters.
    _string_custom_ids : `None` or `tuple` of `str`
        The custom id-s to wait for.
    _regex_custom_ids : `None` or `tuple` of `re.Pattern`.
        Regex pattern to match custom-ids.
    """
    __slots__ = ('_command_function', '_exception_handlers', '_parent_reference', '_parameter_converters',
        '_regex_custom_ids', '_string_custom_ids')

    @classmethod
    def from_class(cls, klass):
        """
        Creates a new ``ComponentCommand`` instance from the given `klass`.

        Parameters
        ----------
        klass : `type`
            The class to create component command from.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            If any attribute's type is incorrect.
        ValueError
            If any attribute's value is incorrect.
        """
        return create_event_from_class(cls, klass, SLASH_COMMAND_PARAMETER_NAMES, SLASH_COMMAND_NAME_NAME,
            SLASH_COMMAND_COMMAND_NAME)

    def __new__(cls, func, custom_id):
        """
        Creates a new ``ComponentCommand`` instance with the given parameters

        Parameters
        ----------
        func : `None` or `async-callable`, Optional
            The function used as the command when using the respective slash command.
        custom_id : `str`, (`list` or `set`) of `str`, `tuple` of (`str`, (`list` or `set`) of `str`)
            Custom id to match by the component command.

        Returns
        -------
        self : ``ComponentCommand`` or ``Router``

        Raises
        ------
        TypeError
            - If `func` is not async callable, neither cannot be instanced to async.
            - If `func` accepts keyword only parameters.
            - If `func` accepts `*args`.
            - If `func` accepts `**kwargs`
            - If `custom_id`'s type is incorrect.
        ValueError:
            - If no `custom_id` was received.
            - If `custom_id` contains incorrect value.
        """
        if (func is not None) and isinstance(func, SlasherCommandWrapper):
            command, wrappers = func.fetch_function_and_wrappers_back()
        else:
            command = func
            wrappers = None

        route_to = 0
        custom_id, route_to = _check_maybe_route('name', custom_id, route_to, _validate_custom_ids)

        command, parameter_converters = get_component_command_parameter_converters(command)

        if route_to:
            custom_id = route_value(custom_id, route_to)

            router = []

            for custom_id in custom_id:
                string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

                self = object.__new__(cls)
                self._command_function = command
                self._parameter_converters = parameter_converters
                self._string_custom_ids = string_custom_ids
                self._regex_custom_ids = regex_custom_ids
                self._parent_reference = None
                self._exception_handlers = None

                if (wrappers is not None):
                    for wrapper in wrappers:
                        wrapper.apply(self)

                router.append(self)

            return Router(router)
        else:
            string_custom_ids, regex_custom_ids = split_and_check_satisfaction(custom_id, parameter_converters)

            self = object.__new__(cls)
            self._command_function = command
            self._parameter_converters = parameter_converters
            self._string_custom_ids = string_custom_ids
            self._regex_custom_ids = regex_custom_ids
            self._parent_reference = None
            self._exception_handlers = None

            if (wrappers is not None):
                for wrapper in wrappers:
                    wrapper.apply(self)

            return self

    def __repr__(self):
        """Returns the component command's representation."""
        repr_parts = ['<', self.__class__.__name__]

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is None):
            field_added = False
        else:
            field_added = True

            repr_parts.append(' string_custom_ids=[')
            index = 0
            limit = len(string_custom_ids)

            while True:
                string_custom_id = string_custom_ids[index]
                repr_parts.append(repr(string_custom_id))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            if field_added:
                repr_parts.append(', ')

            repr_parts.append(' regex_custom_ids=[')
            index = 0
            limit = len(regex_custom_ids)

            while True:
                regex_custom_id = regex_custom_ids[index]
                repr_parts.append(repr(regex_custom_id.pattern.pattern))

                index += 1
                if index == limit:
                    break

                repr_parts.append(', ')
                continue

            repr_parts.append(']')

        return ''.join(repr_parts)

    async def __call__(self, client, interaction_event, regex_match):
        """
        Calls the component command.

        This method is a coroutine.

        Parameters
        ----------
        client : ``Client``
            The respective client who received the event.
        interaction_event : ``InteractionEvent``
            The received interaction event.
        regex_match : `None` or ``RegexMatch``
            The matched regex if applicable.
        """
        parameters = []

        for parameter_converter in self._parameter_converters:
            try:
                parameter = await parameter_converter(client, interaction_event, regex_match)
            except BaseException as err:
                exception = err
            else:
                parameters.append(parameter)
                continue

            await handle_command_exception(
                self,
                client,
                interaction_event,
                exception,
            )
            return

        command_coroutine = self._command_function(*parameters)

        await handle_command_exception(
            self,
            client,
            interaction_event,
            exception,
        )
        return

    def __hash__(self):
        """Returns the component command's hash value."""
        hash_value = 0

        string_custom_ids = self._string_custom_ids
        if (string_custom_ids is not None):
            hash_value ^= hash(string_custom_ids)

        regex_custom_ids = self._regex_custom_ids
        if (regex_custom_ids is not None):
            hash_value ^= hash(regex_custom_ids)

        command_function = self._command_function
        try:
            command_hash_value = hash(command_function)
        except KeyError:
            command_hash_value = object.__hash__(command_function)

        hash_value ^= command_hash_value
        return hash_value

    def __eq__(self, other):
        """Returns whether self equals to other."""
        if type(self) is not type(other):
            return NotImplemented

        if self._command_function != other._command_function:
            return False

        if self._string_custom_ids != other._string_custom_ids:
            return False

        if self._regex_custom_ids != other._regex_custom_ids:
            return False

        if self._exception_handlers != other._exception_handlers:
            return False

        return True

    def copy(self):
        """
        Copies the component command.

        Returns
        -------
        new : ``ComponentCommand``
        """
        new = object.__new__(type(self))
        new._command_function = self._command_function
        new._parameter_converters = self._parameter_converters
        new._string_custom_ids = self._string_custom_ids
        new._regex_custom_ids = self._regex_custom_ids
        new._parent_reference = None

        exception_handlers = self._exception_handlers
        if (exception_handlers is not None):
            exception_handlers = exception_handlers.copy()
        new._exception_handlers = exception_handlers

        return new

    def error(self, exception_handler=None, *, first=False):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `None` or `CoroutineFunction`, Optional
            Exception handler to register.
        first : `bool`, Optional (Keyword Only)
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler / wrapper : `CoroutineFunction` / `functools.partial`
            If `exception_handler` is not given, returns a wrapper.
        """
        if exception_handler is None:
            return partial_func(_register_exception_handler, first)

        return self._register_exception_handler(exception_handler, first)

    def _register_exception_handler(self, exception_handler, first):
        """
        Registers an exception handler to the ``SlasherApplicationCommandCategory``.

        Parameters
        ----------
        exception_handler : `CoroutineFunction`
            Exception handler to register.
        first : `bool`
            Whether the exception handler should run first.

        Returns
        -------
        exception_handler : `CoroutineFunction`
        """
        test_exception_handler(exception_handler)

        exception_handlers = self._exception_handlers
        if exception_handlers is None:
            self._exception_handlers = exception_handlers = []

        if first:
            exception_handlers.insert(0, exception_handler)
        else:
            exception_handlers.append(exception_handler)

        return exception_handler


# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


self.code = code
        self.value = value
        self.url = url
        self.stack = stack

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):

        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }

        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty