__all__ = ()

from os.path import split as split_path
from collections import deque

from ...backend.utils import to_json
from ...backend.export import include
from ...backend.formdata import Formdata

from ..core import MESSAGES, CHANNELS, GUILDS, USERS, STICKERS, SCHEDULED_EVENTS
from ..message import Message, MessageReference, MessageRepr
from ..user import ClientUserBase
from ..channel import ChannelText, ChannelStage
from ..embed import EmbedBase
from ..utils import random_id
from ..bases import maybe_snowflake_pair, maybe_snowflake, maybe_snowflake_token_pair
from ..guild import Guild, GuildDiscovery
from ..oauth2 import Achievement
from ..role import Role
from ..stage import Stage
from ..webhook import Webhook
from ..emoji import Emoji, parse_reaction
from ..sticker import Sticker
from ..scheduled_event import ScheduledEvent


ComponentBase = include('ComponentBase')
ComponentType = include('ComponentType')
ComponentRow = include('ComponentRow')

def get_components_data(components, is_edit):
    """
    Gets component data from the given components.
    
    Parameters
    ----------
    components : `None`, ``ComponentBase``, (`set`, `list`) of \
            (``ComponentBase``, (`set`, `list`) of ``ComponentBase``)
        Components to be attached to a message.
    is_edit : `bool`
        Whether the processed `components` fields are for message edition. At this case passing `None` will
        remove them.
    
    Returns
    -------
    component_datas : `None` ot `list` of (`dict` of (`str`, `Any`) items)
        The generated data if any.
    
    Raises
    ------
    TypeError
        - If `components` was not given neither as `None`, ``ComponentBase``, (`list`, `tuple`) of ``ComponentBase``
            instances.
    AssertionError
        - If `components` contains a non ``ComponentBase`` element.
    """
    
    # Components check order:
    # 1.: None -> None || []
    # 2.: Ellipsis -> None || Ellipsis
    # 2.: ComponentBase -> [component.to_data()]
    # 3.: (list, tuple) of ComponentBase, (list, tuple) of ComponentBase -> [component.to_data(), ...] / None
    # 4.: raise
    
    if components is None:
        if is_edit:
            component_datas = []
        else:
            component_datas = None
    
    elif components is ...:
        if is_edit:
            component_datas = ...
        else:
            component_datas = None
    
    else:
        if isinstance(components, ComponentBase):
            if components.type is not ComponentType.row:
                components = ComponentRow(components)
                
            component_datas = [components.to_data()]
        elif isinstance(components, (list, tuple)):
            component_datas = None
            
            for component in components:
                if isinstance(component, ComponentBase):
                    if component.type is not ComponentType.row:
                        component = ComponentRow(component)
                
                elif isinstance(component, (list, tuple)):
                    component = ComponentRow(*component)
                
                else:
                    raise TypeError(f'`components` contains a non `{ComponentBase.__name__}` or as `list`, `tuple` of '
                        f'`{ComponentBase.__name__}` instances, got {components.__class__.__name__}')
                
                if component_datas is None:
                    component_datas = []
                
                component_datas.append(component.to_data())
                continue
        
        else:
            raise TypeError(f'`components` can be given as `{ComponentBase.__name__}` or as `list`, `tuple` of '
                f'(`{ComponentBase.__name__}` or (`list`, `tuple`) of `{ComponentBase.__name__}`), '
                f'got {components.__class__.__name__}')
    
    return component_datas


def validate_message_to_delete(message):
    """
    Validates a message to delete.
    
    Parameters
    ----------
    message : ``Message``, ``MessageReference``, ``MessageRepr``, `tuple` (`int`, `int`)
        The message to validate for deletion.
    
    Returns
    -------
    channel_id : `int`
        The channel's identifier where the message is.
    message_id : `int`
        The message's identifier.
    message : `None` or ``Message``
        The referenced message if found.
    
    Raises
    ------
    TypeError
        If message was not given neither as ``Message``, ``MessageReference``, ``MessageRepr``, neither as
        `tuple` (`int`, `int`).
    """
    if isinstance(message, Message):
        channel_id = message.channel_id
        message_id = message.id
    else:
        if isinstance(message, MessageRepr):
            channel_id = message.channel_id
            message_id = message.id
        elif isinstance(message, MessageReference):
            channel_id = message.channel_id
            message_id = message.message_id
        else:
            snowflake_pair = maybe_snowflake_pair(message)
            if snowflake_pair is None:
                raise TypeError(f'`message` should have be given as `{Message.__name__}` or as '
                    f'`{MessageRepr.__name__}`, `{MessageReference.__name__}`, or as `tuple` of (`int`, `int`), '
                    f'got {message.__class__.__name__}.')
            
            channel_id, message_id = snowflake_pair
        
        message = MESSAGES.get(message, None)
    
    return channel_id, message_id, message


def create_file_form(data, file):
    """
    Creates a `multipart/form-data` form from the message's data and from the file data. If there is no files to
    send, will return `None` to tell the caller, that nothing is added to the overall data.
    
    Parameters
    ----------
    data : `dict` of `Any`
        The data created by the ``.message_create`` method.
    file : `dict` of (`file-name`, `io`) items, `list` of (`file-name`, `io`) elements, tuple (`file-name`, `io`), `io`
        The files to send.
    
    Returns
    -------
    form : `None` or `Formdata`
        Returns a `Formdata` of the files and from the message's data. If there are no files to send, returns `None`
        instead.
    
    Raises
    ------
    ValueError
        If more than `10` file is registered to be sent.
    
    Notes
    -----
    Accepted `io` types with check order are:
    - ``BodyPartReader`` instance
    - `bytes`, `bytearray`, `memoryview` instance
    - `str` instance
    - `BytesIO` instance
    - `StringIO` instance
    - `TextIOBase` instance
    - `BufferedReader`, `BufferedRandom` instance
    - `IOBase` instance
    - ``AsyncIO`` instance
    - `async-iterable`
    
    Raises `TypeError` at the case of invalid `io` type.
    
    There are two predefined data types specialized to send files:
    - ``ReuBytesIO``
    - ``ReuAsyncIO``
    
    If a buffer is sent, then when the request is done, it is closed. So if the request fails, we would not be
    able to resend the file, except if we have a data type, what instead of closing on `.close()` just seeks to
    `0` (or later if needed) on close, instead of really closing instantly. These data types implement a
    `.real_close()` method, but they do `real_close` on `__exit__` as well.
    """
    form = Formdata()
    form.add_field('payload_json', to_json(data))
    files = []
    
    # checking structure
    
    # case 1 dict like
    if hasattr(type(file), 'items'):
        files.extend(file.items())
    
    # case 2 tuple => file, filename pair
    elif isinstance(file, tuple):
        files.append(file)
    
    # case 3 list like
    elif isinstance(file, (list, deque)):
        for element in file:
            if type(element) is tuple:
                name, io = element
            else:
                io = element
                name = ''
            
            if not name:
                #guessing name
                name = getattr(io, 'name', '')
                if name:
                    _, name = split_path(name)
                else:
                    name = str(random_id())
            
            files.append((name, io),)
    
    #case 4 file itself
    else:
        name = getattr(file, 'name', '')
        #guessing name
        if name:
            _, name = split_path(name)
        else:
            name = str(random_id())
        
        files.append((name, file),)
    
    # checking the amount of files
    # case 1 one file
    if len(files) == 1:
        name, io = files[0]
        form.add_field('file', io, filename=name, content_type='application/octet-stream')
    # case 2, no files -> return None, we should use the already existing data
    elif len(files) == 0:
        return None
    # case 3 maximum 10 files
    elif len(files) < 11:
        for index, (name, io) in enumerate(files):
            form.add_field(f'file{index}s', io, filename=name, content_type='application/octet-stream')
    
    # case 4 more than 10 files
    else:
        raise ValueError('You can send maximum 10 files at once.')
    
    return form


def add_file_to_message_data(message_data, file, contains_content):
    """
    Adds files to the message data creating a form data if applicable.
    
    Parameters
    ----------
    message_data : `dict` of (`str`, `Any`) items
        The message's payload to send.
    file : `None` or `dict` of (`file-name`, `io`) items, `list` of (`file-name`, `io`) elements, \
            tuple (`file-name`, `io`), `io`
        The files to send.
    contains_content : `bool`
        Whether the message already contains any content.
    
    Returns
    -------
    message_data : `None` or `dict`, `Formdata`
        Returns a `Formdata` if the message contains attachments, `dict` if contains any content and `None` if
        not.
    
    Raises
    ------
    ValueError
        If more than `10` file is registered to be sent.
    """
    if (file is None):
        if not contains_content:
            message_data = None
    else:
        form = create_file_form(message_data, file)
        if (form is None) and (not contains_content):
            message_data = None
        else:
            message_data = form
    
    return message_data


def validate_content_and_embed(content, embed, is_edit):
    """
    Validates the given content and embed fields of a message creation or edition.
    
    Parameters
    ----------
    content : `str`, ``EmbedBase`` or `Any`, Optional
        The content of the message.
        
        
        If given as ``EmbedBase`` instance, then the message's embeds will be edited with it.
    embed : `None`, ``EmbedBase`` instance or `list` of ``EmbedBase`` instances, Optional (Keyword only)
        The new embedded content of the message. By passing it as `None`, you can remove the old.
        
        > If `embed` and `content` parameters are both given as  ``EmbedBase`` instance, then `AssertionError` is
        raised.
    is_edit : `bool`
        Whether the processed `content` and `embed` fields are for message edition. At this case passing `None` will
        remove them.
    
    Returns
    -------
    content : `Ellipsis`, `None`, `str`
        The message's content.
    embed : `Ellipsis`, `None`, ``EmbedBase``, (`list` or `tuple`) of ``EmbedBase``
        The messages embeds.
    
    Raises
    ------
    TypeError
        If `embed` was not given neither as ``EmbedBase`` nor as `list` or `tuple` of ``EmbedBase`` instances.
    AssertionError
        - If `embed` contains a non ``EmbedBase`` element.
        - If both `content` and `embed` fields are embeds.
    """
    # Embed check order:
    # 1.: None
    # 2.: Ellipsis -> None || Ellipsis
    # 3.: Embed : -> embed || [embed]
    # 4.: list of Embed -> embed[0] || embed[:10] or None
    # 5.: raise
    if embed is None:
        pass
    
    elif embed is ...:
        if not is_edit:
            embed = None
    
    elif isinstance(embed, EmbedBase):
        embed = [embed]
    
    elif isinstance(embed, (list, tuple)):
        if embed:
            if __debug__:
                for embed_element in embed:
                    if not isinstance(embed_element, EmbedBase):
                        raise AssertionError(f'`embed` was given as a `list` or `tuple`, but it\'s it contains a non '
                            f'`{EmbedBase.__name__}` instance element, got {embed_element.__class__.__name__}.')
            
            embed = embed[:10]
        else:
            embed = None
    
    else:
        raise TypeError(f'`embed` was not given as `{EmbedBase.__name__}` instance, neither as a `list`  or `tuple` of '
            f'{EmbedBase.__name__} instances, got {embed.__class__.__name__}.')
    
    # Content check order:
    # 1.: None -> None || ''
    # 2.: Ellipsis -> None || Ellipsis
    # 3.: str
    # 4.: Embed -> embed = content || [content]
    # 5.: list of Embed -> embed = content[0] || content[:10]
    # 6.: object -> str(content)
    
    if content is None:
        if is_edit:
            content = ''
    
    elif content is ...:
        if not is_edit:
            content = None
    
    elif isinstance(content, str):
        pass
    
    elif isinstance(content, EmbedBase):
        if __debug__:
            if (embed is not (... if is_edit else None)):
                raise AssertionError(f'Multiple embeds were given, got content={content!r}, embed={embed!r}.')
        
        embed = [content]
        
        if is_edit:
            content = ...
        else:
            content = None
    
    else:
        # Check for list of embeds as well.
        if isinstance(content, (list, tuple)):
            if content:
                for element in content:
                    if isinstance(element, EmbedBase):
                        continue
                    
                    is_list_of_embeds = False
                    break
                else:
                    is_list_of_embeds = True
            else:
                is_list_of_embeds = False
        else:
            is_list_of_embeds = False
        
        if is_list_of_embeds:
            if __debug__:
                if (embed is not (... if is_edit else None)):
                    raise AssertionError(f'Multiple embeds were given, got content={content!r}, embed={embed!r}.')
            
            embed = content[:10]
            
            if is_edit:
                content = ...
            else:
                content = None
        else:
            content = str(content)
    
    return content, embed


def get_channel_id(channel, channel_type):
    """
    Gets the channel's identifier from the given channel or of it's identifier.
    
    Parameters
    ----------
    channel : `channel_type`, `int`
        The channel, or it's identifier.
    channel_type : `type`
        The expected channel type of `channel`.
    
    Returns
    -------
    channel_id : `int`
        The channel's identifier.
    
    Raises
    ------
    TypeError
        If `channel`'s type is incorrect.
    """
    if isinstance(channel, channel_type):
        channel_id = channel.id
    
    else:
        channel_id = maybe_snowflake(channel)
        if channel_id is None:
            raise TypeError(f'The channel can be either given as `{channel_type.__name__}` or as `int` instance, '
                f'got {channel.__class__.__name__}.')
    
    return channel_id


def get_guild_and_guild_text_channel_id(channel):
    """
    Gets guild text channel identifier and it's guild from the given text channel or of it's identifier.
    
    Parameters
    ----------
    channel : ``ChannelText``, `int`
        The channel, or it's identifier.
    
    Returns
    -------
    guild : ``Guild`` or `None`
        The respective guild if any found.
    channel_id : `int`
        The channel's identifier.
    
    Raises
    ------
    TypeError
        If `channel`'s type is incorrect.
    """
    while True:
        if isinstance(channel, ChannelText):
            channel_id = channel.id
            guild = channel.guild
            break
        
        channel_id = maybe_snowflake(channel)
        if (channel_id is not None):
            try:
                channel = CHANNELS[channel_id]
            except KeyError:
                guild = None
                break
            
            if isinstance(channel, ChannelText):
                guild = channel.guild
                break
        
        raise TypeError(f'`channel` can be either given as `{ChannelText.__name__}` or as `int` instance, '
            f'got {channel.__class__.__name__}.')
    
    return guild, channel_id


def get_guild_id_and_channel_id(channel, channel_type):
    """
    Gets the channel's and it's guild's identifier from the given channel or of it's identifier.
    
    Parameters
    ----------
    channel : `channel_type` or `tuple` (`int`, `int`)
        The role, or `guild-id`, `role-id` pair.
    channel_type : `type`
        The expected type of `channel`.
    
    Returns
    -------
    snowflake_pair : `None` or `tuple` (`int`, `int`)
        The channel's guild's and it's own identifier if applicable.
    
    Raises
    ------
    TypeError
        If `channel`'s type is incorrect.
    """
    if isinstance(channel, channel_type):
        guild = channel.guild
        if guild is None:
            snowflake_pair = None
        else:
            snowflake_pair = guild.id, channel.id
    else:
        snowflake_pair = maybe_snowflake_pair(channel)
        if snowflake_pair is None:
            raise TypeError(f'`channel` can be given as `{channel_type.__name__}`, or as '
                f'`tuple` (`int`, `int`), got {channel.__class__.__name__}.')
        
    return snowflake_pair


def get_channel_and_id(channel, channel_type):
    """
    Gets thread channel and it's identifier from the given thread channel or of it's identifier.
    
    Parameters
    ----------
    channel : ``ChannelThread``, `int`
        The channel, or it's identifier.
    channel_type : `type`
        The expected type of `channel`.
    
    Returns
    -------
    channel : ``channel_type``, `None`
        The channel if found.
    channel_id : `int`
        The channel's identifier.
    
    Raises
    ------
    TypeError
        If `channel`'s type is incorrect.
    """
    while True:
        if isinstance(channel, channel_type):
            channel_id = channel.id
            break
        
        channel_id = maybe_snowflake(channel)
        if (channel_id is not None):
            try:
                channel = CHANNELS[channel_id]
            except KeyError:
                channel = None
                break
            
            if isinstance(channel, channel_type):
                break
        
        raise TypeError(f'`channel` can be either given as `{channel_type.__name__}` or as `int` instance, '
            f'got {channel.__class__.__name__}.')
    
    return channel, channel_id


def get_stage_channel_id(stage):
    """
    Gets stage's channel's identifier from the given stage or of it's identifier.
    
    Parameters
    ----------
    stage : `Stage`, ``ChannelStage``, `int`
        The stage, or it's identifier.
    
    Returns
    -------
    channel_id : `int`
        The channel's identifier.
    
    Raises
    ------
    TypeError
        If `stage`'s type is incorrect.
    """
    if isinstance(stage, Stage):
        channel_id = stage.channel.id
    elif isinstance(stage, ChannelStage):
        channel_id = stage.id
    else:
        channel_id = maybe_snowflake(stage)
        if channel_id is None:
            raise TypeError(f'`stage` can be given as `{Stage.__name__}`, `{ChannelStage.__name__}`, or as '
                f'int` instance, got {stage.__class__.__name__}.')
    
    return channel_id


def get_user_id(user):
    """
    Gets user identifier from the given user or of it's identifier.
    
    Parameters
    ----------
    user : ``ClientUserBase``, `int`
        The user, or it's identifier.
    
    Returns
    -------
    user_id : `int`
        The user's identifier.
    
    Raises
    ------
    TypeError
        If `user`'s type is incorrect.
    """
    if isinstance(user, ClientUserBase):
        user_id = user.id
    
    else:
        user_id = maybe_snowflake(user)
        if user_id is None:
            raise TypeError(f'`user` can be either given as `{ClientUserBase.__name__}` or as `int` instance, '
                f'got {user.__class__.__name__}.')
    
    return user_id


def get_user_and_id(user):
    """
    Gets user and it's identifier from the given user or of it's identifier.
    
    Parameters
    ----------
    user : ``ClientUserBase``, `int`
        The user, or it's identifier.
    
    Returns
    -------
    user : ``ClientUserBase`` or `None`
        The user if found.
    user_id : `int`
        The user's identifier.
    
    Raises
    ------
    TypeError
        If `user`'s type is incorrect.
    """
    while True:
        if isinstance(user, ClientUserBase):
            user_id = user.id
            break
        
        user_id = maybe_snowflake(user)
        if (user_id is not None):
            try:
                user = USERS[user_id]
            except KeyError:
                user = None
                break
            
            if isinstance(user, ClientUserBase):
                break
        
        raise TypeError(f'`user` can be either given as `{ClientUserBase.__name__}` or as `int` instance, '
            f'got {user.__class__.__name__}.')
        
    return user, user_id


def get_user_id_nullable(user):
    """
    Gets user identifier from the given user or of it's identifier. The user can be given as `None`. At that case
    `user_id` will default to `0`.
    
    Parameters
    ----------
    user : `None`, ``ClientUserBase``, `int`
        The user, or it's identifier.
    
    Returns
    -------
    user_id : `int`
        The user's identifier.
    
    Raises
    ------
    TypeError
        If `user`'s type is incorrect.
    """
    if user is None:
        user_id = 0
    
    elif isinstance(user, ClientUserBase):
        user_id = user.id
    
    else:
        user_id = maybe_snowflake(user)
        if user_id is None:
            raise TypeError(f'`user` can be either given as `{ClientUserBase.__name__}` or as `int` instance, '
                f'got {user.__class__.__name__}.')
    
    return user_id


def get_guild_id(guild):
    """
    Gets the guild's identifier from the given guild or of it's identifier.
    
    Parameters
    ----------
    guild : ``Guild``, `int`
        The guild, or it's identifier.
    
    Returns
    -------
    guild_id : `int`
        The guild's identifier.
    
    Raises
    ------
    TypeError
        If `guild`'s type is incorrect.
    """
    if isinstance(guild, Guild):
        guild_id = guild.id
    else:
        guild_id = maybe_snowflake(guild)
        if guild_id is None:
            raise TypeError(f'`guild` can be given as `{Guild.__name__}` or `int` instance, got '
                f'{guild.__class__.__name__}.')
    
    return guild_id


def get_guild_and_id(guild):
    """
    Gets the guild and it's identifier from the given guild or of it's identifier.
    
    Parameters
    ----------
    guild : ``Guild``, `int`
        The guild, or it's identifier.
    
    Returns
    -------
    guild : ``Guild`` or `None`
        The guild if found.
    guild_id : `int`
        The guild's identifier.
    
    Raises
    ------
    TypeError
        If `guild`'s type is incorrect.
    """
    if isinstance(guild, Guild):
        guild_id = guild.id
    else:
        guild_id = maybe_snowflake(guild)
        if guild_id is None:
            raise TypeError(f'`guild` can be given as `{Guild.__name__}` or `int` instance, got '
                f'{guild.__class__.__name__}.')
        
        guild = GUILDS.get(guild_id, None)
    
    return guild, guild_id


def get_guild_discovery_and_id(guild):
    """
    Gets the guild discovery and it's identifier from the given guild or it's identifier.
    
    Parameters
    ----------
    guild : ``Guild``, `int`
        The guild, or it's identifier.
    
    Returns
    -------
    guild : ``GuildDiscovery`` or `None`
        The guild or the
    guild_id : `int`
        The guild's identifier.
    
    Raises
    ------
    TypeError
        If `guild`'s type is incorrect.
    """
    if isinstance(guild, Guild):
        guild_id = guild.id
        guild_discovery = None
    elif isinstance(guild, GuildDiscovery):
        guild_id = guild.guild.id
        guild_discovery = guild
    else:
        guild_id = maybe_snowflake(guild)
        if guild_id is None:
            raise TypeError(f'`guild` can be given as `{Guild.__name__}`, `{GuildDiscovery.__name__}` or `int` '
                f'instance, got {guild.__class__.__name__}.')
        
        guild_discovery = None
    
    return guild_discovery, guild_id


def get_achievement_id(achievement):
    """
    Gets the achievement identifier from the given achievement or of it's identifier.
    
    Parameters
    ----------
    achievement : ``Achievement``, `int`
        The achievement, or it's identifier.
    
    Returns
    -------
    achievement_id : `int`
        The achievement's identifier.
    
    Raises
    ------
    TypeError
        If `achievement`'s type is incorrect.
    """
    if isinstance(achievement, Achievement):
        achievement_id = achievement.id
    else:
        achievement_id = maybe_snowflake(achievement)
        if achievement_id is None:
            raise TypeError(f'`achievement` can be given as `{Achievement.__name__}` or `int` instance, got '
                f'{achievement.__class__.__name__}.')
    
    return achievement_id


def get_achievement_and_id(achievement):
    """
    Gets the achievement and it's identifier from the given achievement or of it's identifier.
    
    Parameters
    ----------
    achievement : ``Achievement``, `int`
        The achievement, or it's identifier.
    
    Returns
    -------
    achievement : ``Achievement``, `None`
        The achievement if found.
    achievement_id : `int`
        The achievement's identifier.
    
    Raises
    ------
    TypeError
        If `achievement`'s type is incorrect.
    """
    if isinstance(achievement, Achievement):
        achievement_id = achievement.id
    else:
        achievement_id = maybe_snowflake(achievement)
        if achievement_id is None:
            raise TypeError(f'`achievement` can be given as `{Achievement.__name__}` or `int` instance, got '
                f'{achievement.__class__.__name__}.')
        
        achievement = None
    
    return achievement, achievement_id


def get_channel_id_and_message_id(message):
    """
    Gets the message's channel's and it's own identifier.
    
    Parameters
    ----------
    message : ``Message``, ``MessageRepr``, ``MessageReference``, `tuple` (`int`, `int`)
        The message or it' representation.
    
    Returns
    -------
    channel_id : `int`
        The message's channel's identifier.
    message_id : `int`
        The message's identifier.
    
    Raises
    ------
    TypeError
        If `message`'s type is incorrect.
    """
    # 1.: Message
    # 2.: MessageRepr
    # 3.: MessageReference
    # 4.: None -> raise
    # 5.: `tuple` (`int`, `int`)
    # 6.: raise
    if isinstance(message, Message):
        channel_id = message.channel_id
        message_id = message.id
    elif isinstance(message, MessageRepr):
        channel_id = message.channel_id
        message_id = message.id
    elif isinstance(message, MessageReference):
        channel_id = message.channel_id
        message_id = message.message_id
    elif message is None:
        raise TypeError('`message` was given as `None`. Make sure to use `Client.message_create` with giving '
            'content and using a cached channel.')
    else:
        snowflake_pair = maybe_snowflake_pair(message)
        if snowflake_pair is None:
            raise TypeError(f'`message` can be given as `{Message.__name__}` or as '
                f'`{MessageRepr.__name__}`, `{MessageReference.__name__}` or as `tuple` of (`int`, `int`), got '
                f'`{message.__class__.__name__}`.')
        
        channel_id, message_id = snowflake_pair
    
    return channel_id, message_id


def get_role_id(role):
    """
    Gets the role identifier from the given role or of it's identifier.
    
    Parameters
    ----------
    role : ``Role``, `int`
        The role, or it's identifier.
    
    Returns
    -------
    role_id : `int`
        The role's identifier.
    
    Raises
    ------
    TypeError
        If `role`'s type is incorrect.
    """
    if isinstance(role, Role):
        role_id = role.id
    else:
        role_id = maybe_snowflake(role)
        if role_id is None:
            raise TypeError(f'`role` can be given as `{Role.__name__}` or `int` instance, got '
                f'{role.__class__.__name__}.')
    
    return role_id


def get_guild_id_and_role_id(role):
    """
    Gets the role's and it's guild's identifier from the given role or of a `guild-id`, `role-id` pair.
    
    Parameters
    ----------
    role : ``Role`` or `tuple` (`int`, `int`)
        The role, or `guild-id`, `role-id` pair.
    
    Returns
    -------
    snowflake_pair : `None` or `tuple` (`int`, `int`)
        The role's guild's and it's own identifier if applicable.
    
    Raises
    ------
    TypeError
        If `role`'s type is incorrect.
    """
    if isinstance(role, Role):
        guild = role.guild
        if guild is None:
            snowflake_pair = None
        else:
            snowflake_pair = guild.id, role.id
    
    else:
        snowflake_pair = maybe_snowflake_pair(role)
        if snowflake_pair is None:
            raise TypeError(f'`role` can be given as `{Role.__name__}`, or as `tuple` (`int`, `int`), got '
                f'{role.__class__.__name__}.')
    
    return snowflake_pair


def get_webhook_id(webhook):
    """
    Gets the webhook's identifier from the given webhook or of it's identifier.
    
    Parameters
    ----------
    webhook : ``Webhook``, `int`
        The webhook, or it's identifier.
    
    Returns
    -------
    webhook_id : `int`
        The webhook's identifier.
    
    Raises
    ------
    TypeError
        If `webhook`'s type is incorrect.
    """
    if isinstance(webhook, Webhook):
        webhook_id = webhook.id
    else:
        webhook_id = maybe_snowflake(webhook)
        if webhook_id is None:
            raise TypeError(f'`webhook` can be given as `{Webhook.__name__}` or `int` instance, got '
                f'{webhook.__class__.__name__}.')
    
    return webhook_id


def get_webhook_and_id(webhook):
    """
    Gets the webhook and it's identifier from the given webhook or of it's identifier.
    
    Parameters
    ----------
    webhook : ``Webhook``, `int`
        The webhook, or it's identifier.
    
    Returns
    -------
    webhook : ``Webhook`` or `None`
        The webhook if any.
    webhook_id : `int`
        The webhook's identifier.
    
    Raises
    ------
    TypeError
        If `webhook`'s type is incorrect.
    """
    while True:
        if isinstance(webhook, Webhook):
            webhook_id = webhook.id
            break
        
        webhook_id = maybe_snowflake(webhook)
        if (webhook_id is not None):
            try:
                webhook = USERS[webhook_id]
            except KeyError:
                webhook = None
                break
            
            if isinstance(webhook, Webhook):
                break
            
            raise TypeError(f'`webhook` can be given as `{Webhook.__name__}` or `int` instance, got '
                f'{webhook.__class__.__name__}.')
    
    return webhook, webhook_id


def get_webhook_id_token(webhook):
    """
    Gets the webhook's identifier and token from the given webhook or it's token, identifier pair.
    
    Parameters
    ----------
    webhook : ``Webhook``, `tuple` (`int`, `str`)
        The webhook or it's id and token.
    
    Returns
    -------
    webhook_id : `int`
        The webhook's identifier.
    webhook_token : `str`
        The webhook's token.
    
    Raises
    ------
    TypeError
        If `webhook`'s type is incorrect.
    """
    if isinstance(webhook, Webhook):
        snowflake_token_pair = webhook.id, webhook.token
    else:
        snowflake_token_pair = maybe_snowflake_token_pair(webhook)
        if (snowflake_token_pair is None):
            raise TypeError(f'`webhook` can be given either as `{Webhook.__name__}` or as `tuple` (`int`, `str`), '
                f'got {webhook.__class__.__name__}.')
    
    return snowflake_token_pair


def get_webhook_and_id_token(webhook):
    """
    Gets the webhook, it's identifier and token from the given webhook or it's token, identifier pair.
    
    Parameters
    ----------
    webhook : ``Webhook``, `tuple` (`int`, `str`)
        The webhook or it's id and token.
    
    Returns
    -------
    webhook : ``Webhook`` or `None`
        The webhook if any.
    webhook_id : `int`
        The webhook's identifier.
    webhook_token : `str`
        The webhook's token.
    
    Raises
    ------
    TypeError
        If `webhook`'s type is incorrect.
    """
    while True:
        if isinstance(webhook, Webhook):
            webhook_id = webhook.id
            webhook_token = webhook.token
            break
        
        snowflake_token_pair = maybe_snowflake_token_pair(webhook)
        if (snowflake_token_pair is not None):
            webhook_id, webhook_token = snowflake_token_pair
            
            try:
                webhook = USERS[webhook_id]
            except KeyError:
                webhook = None
                break
            
            if isinstance(webhook, Webhook):
                break
        
        raise TypeError(f'`webhook` can be given either as `{Webhook.__name__}` or as `tuple` (`int`, `str`), '
            f'got {webhook.__class__.__name__}.')
    
    return webhook, webhook_id, webhook_token


def get_reaction(emoji):
    """
    Gets the reaction form of the given emoji.
    
    Parameters
    ----------
    emoji : ``Emoji`` or `str`
        The emoji to get it's reaction form of.
    
    Returns
    -------
    as_reaction : `str`
        The emoji's reaction form.
    
    Raises
    ------
    TypeError
        If `emoji`'s type is incorrect.
    """
    if isinstance(emoji, Emoji):
        as_reaction = emoji.as_reaction
    elif isinstance(emoji, str):
        as_reaction = emoji
    else:
        raise TypeError(f'`emoji` can be given as ``{Emoji.__class__}`` or as `str` instance, got '
            f'{emoji.__class__.__name__}.')
    
    return as_reaction


def get_emoji_from_reaction(emoji):
    """
    Gets emoji from the given emoji or reaction string.
    
    Parameters
    ----------
    emoji : ``Emoji`` or `str`
        The emoji or reaction to get the emoji from.
    
    Returns
    -------
    emoji : ``Emoji``
        The emoji itself.
    
    Raises
    ------
    TypeError
        If `emoji`'s type is incorrect.
    ValueError
        The given `emoji` is not a valid reaction.
    """
    if isinstance(emoji, Emoji):
        pass
    elif isinstance(emoji, str):
        emoji = parse_reaction(emoji)
        if emoji is None:
            raise ValueError(f'The given `emoji` is not a valid reaction, got {emoji!r}.')
    else:
        raise TypeError(f'`emoji` can be given as ``{Emoji.__class__}`` or as `str` instance, got '
            f'{emoji.__class__.__name__}.')
    
    return emoji


def get_guild_id_and_emoji_id(emoji):
    """
    Gets the emoji's and it's guild's identifier from the given emoji.
    
    Parameters
    ----------
    emoji : ``Emoji`` or `tuple` (`int`, `int`)
        The emoji, or `guild-id`, `emoji-id` pair.
    
    Returns
    -------
    snowflake_pair : `None` or `tuple` (`int`, `int`)
        The emoji's guild's and it's own identifier if applicable.
    
    Raises
    ------
    TypeError
        If `emoji`'s type is incorrect.
    """
    if isinstance(emoji, Emoji):
        guild = emoji.guild
        if guild is None:
            snowflake_pair = None
        else:
            snowflake_pair = guild.id, emoji.id
    
    else:
        snowflake_pair = maybe_snowflake_pair(emoji)
        if snowflake_pair is None:
            raise TypeError(f'`emoji` can be given as `{Emoji.__name__}`, or as `tuple` (`int`, `int`), got '
                f'{emoji.__class__.__name__}.')
    
    return snowflake_pair


def get_sticker_and_id(sticker):
    """
    Gets sticker and it's identifier from the given sticker or of it's identifier.
    
    Parameters
    ----------
    sticker : ``Sticker``, `int`
        The sticker, or it's identifier.
    
    Returns
    -------
    sticker : ``Sticker`` or `None`
        The sticker if found.
    sticker_id : `int`
        The sticker's identifier.
    
    Raises
    ------
    TypeError
        If `sticker`'s type is incorrect.
    """
    while True:
        if isinstance(sticker, Sticker):
            sticker_id = sticker.id
            break
        
        sticker_id = maybe_snowflake(sticker)
        if (sticker_id is not None):
            try:
                sticker = STICKERS[sticker_id]
            except KeyError:
                sticker = None
                break
            
            if isinstance(sticker, Sticker):
                break
        
        raise TypeError(f'`sticker` can be either given as `{Sticker.__name__}` or as `int` instance, '
            f'got {sticker.__class__.__name__}.')
        
    return sticker, sticker_id


def get_scheduled_event_id(scheduled_event):
    """
    Gets the scheduled event's identifier from the given guild or of it's identifier.
    
    Parameters
    ----------
    scheduled_event : ``ScheduledEvent``, `int`
        The scheduled event, or it's identifier.
    
    Returns
    -------
    scheduled_event_id : `int`
        The scheduled event's identifier.
    
    Raises
    ------
    TypeError
        If `scheduled_event`'s type is incorrect.
    """
    if isinstance(scheduled_event, ScheduledEvent):
        scheduled_event_id = scheduled_event.id
    else:
        scheduled_event_id = maybe_snowflake(scheduled_event)
        if scheduled_event_id is None:
            raise TypeError(f'`scheduled_event` can be given as `{ScheduledEvent.__name__}` or `int` instance, got '
                f'{scheduled_event.__class__.__name__}.')
    
    return scheduled_event_id


def get_scheduled_event_and_id(scheduled_event):
    """
    Gets the scheduled event's identifier from the given guild or of it's identifier.
    
    Parameters
    ----------
    scheduled_event : ``ScheduledEvent``, `int`
        The scheduled event, or it's identifier.
    
    Returns
    -------
    scheduled_event : `None` or ``ScheduledEvent``
        The scheduled event.
    scheduled_event_id : `int`
        The scheduled event's identifier.
    
    Raises
    ------
    TypeError
        If `scheduled_event`'s type is incorrect.
    """
    if isinstance(scheduled_event, ScheduledEvent):
        scheduled_event =  scheduled_event
        scheduled_event_id = scheduled_event.id
    else:
        scheduled_event_id = maybe_snowflake(scheduled_event)
        if scheduled_event_id is None:
            raise TypeError(f'`scheduled_event` can be given as `{ScheduledEvent.__name__}` or `int` instance, got '
                f'{scheduled_event.__class__.__name__}.')
        
        scheduled_event = SCHEDULED_EVENTS.get(scheduled_event_id, None)
    
    return  scheduled_event, scheduled_event_id
