import datetime
import time
from enum import Enum
from random import randint, choice
from urllib.parse import quote_plus
import aiohttp
import discord
from redbot.core import commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
from redbot.core.utils.chat_formatting import escape, italics, pagify

_ = Translator("General", __file__)


class RPS(Enum):
    rock = "\N{MOYAI}"
    paper = "\N{PAGE FACING UP}"
    scissors = "\N{BLACK SCISSORS}"


class RPSParser:
    def __init__(self, argument):
        argument = argument.lower()
        if argument == "rock":
            self.choice = RPS.rock
        elif argument == "paper":
            self.choice = RPS.paper
        elif argument == "scissors":
            self.choice = RPS.scissors
        else:
            raise


@cog_i18n(_)
class General:
    """General commands."""

    def __init__(self):
        self.stopwatches = {}
        self.ball = [
            _("As I see it, yes"),
            _("It is certain"),
            _("It is decidedly so"),
            _("Most likely"),
            _("Outlook good"),
            _("Signs point to yes"),
            _("Without a doubt"),
            _("Yes"),
            _("Yes – definitely"),
            _("You may rely on it"),
            _("Reply hazy, try again"),
            _("Ask again later"),
            _("Better not tell you now"),
            _("Cannot predict now"),
            _("Concentrate and ask again"),
            _("Don't count on it"),
            _("My reply is no"),
            _("My sources say no"),
            _("Outlook not so good"),
            _("Very doubtful"),
        ]

    @commands.command()
    async def choose(self, ctx, *choices):
        """Chooses between multiple choices.

        To denote multiple choices, you should use double quotes.
        """
        choices = [escape(c, mass_mentions=True) for c in choices]
        if len(choices) < 2:
            await ctx.send(_("Not enough choices to pick from."))
        else:
            await ctx.send(choice(choices))

    @commands.command()
    async def roll(self, ctx, number: int = 100):
        """Rolls random number (between 1 and user choice)

        Defaults to 100.
        """
        author = ctx.author
        if number > 1:
            n = randint(1, number)
            await ctx.send(_("{} :game_die: {} :game_die:").format(author.mention, n))
        else:
            await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention))

    @commands.command()
    async def flip(self, ctx, user: discord.Member = None):
        """Flips a coin... or a user.

        Defaults to coin.
        """
        if user != None:
            msg = ""
            if user.id == ctx.bot.user.id:
                user = ctx.author
                msg = _("Nice try. You think this is funny?\n How about *this* instead:\n\n")
            char = "abcdefghijklmnopqrstuvwxyz"
            tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
            table = str.maketrans(char, tran)
            name = user.display_name.translate(table)
            char = char.upper()
            tran = "∀qƆpƎℲפHIſʞ˥WNOԀQᴚS┴∩ΛMX⅄Z"
            table = str.maketrans(char, tran)
            name = name.translate(table)
            await ctx.send(msg + "(╯°□°）╯︵ " + name[::-1])
        else:
            await ctx.send(_("*flips a coin and... ") + choice([_("HEADS!*"), _("TAILS!*")]))

    @commands.command()
    async def rps(self, ctx, your_choice: RPSParser):
        """Play rock paper scissors"""
        author = ctx.author
        player_choice = your_choice.choice
        red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
        cond = {
            (RPS.rock, RPS.paper): False,
            (RPS.rock, RPS.scissors): True,
            (RPS.paper, RPS.rock): True,
            (RPS.paper, RPS.scissors): False,
            (RPS.scissors, RPS.rock): False,
            (RPS.scissors, RPS.paper): True,
        }

        if red_choice == player_choice:
            outcome = None  # Tie
        else:
            outcome = cond[(player_choice, red_choice)]

        if outcome is True:
            await ctx.send(_("{} You win {}!").format(red_choice.value, author.mention))
        elif outcome is False:
            await ctx.send(_("{} You lose {}!").format(red_choice.value, author.mention))
        else:
            await ctx.send(_("{} We're square {}!").format(red_choice.value, author.mention))

    @commands.command(name="8", aliases=["8ball"])
    async def _8ball(self, ctx, *, question: str):
        """Ask 8 ball a question

        Question must end with a question mark.
        """
        if question.endswith("?") and question != "?":
            await ctx.send("`" + choice(self.ball) + "`")
        else:
            await ctx.send(_("That doesn't look like a question."))

    @commands.command(aliases=["sw"])
    async def stopwatch(self, ctx):
        """Starts/stops stopwatch"""
        author = ctx.author
        if not author.id in self.stopwatches:
            self.stopwatches[author.id] = int(time.perf_counter())
            await ctx.send(author.mention + _(" Stopwatch started!"))
        else:
            tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
            tmp = str(datetime.timedelta(seconds=tmp))
            await ctx.send(author.mention + _(" Stopwatch stopped! Time: **") + tmp + "**")
            self.stopwatches.pop(author.id, None)

    @commands.command()
    async def lmgtfy(self, ctx, *, search_terms: str):
        """Creates a lmgtfy link"""
        search_terms = escape(
            search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
        )
        await ctx.send("https://lmgtfy.com/?q={}".format(search_terms))

    @commands.command(hidden=True)
    @commands.guild_only()
    async def hug(self, ctx, user: discord.Member, intensity: int = 1):
        """Because everyone likes hugs

        Up to 10 intensity levels."""
        name = italics(user.display_name)
        if intensity <= 0:
            msg = "(っ˘̩╭╮˘̩)っ" + name
        elif intensity <= 3:
            msg = "(っ´▽｀)っ" + name
        elif intensity <= 6:
            msg = "╰(*´︶`*)╯" + name
        elif intensity <= 9:
            msg = "(つ≧▽≦)つ" + name
        elif intensity >= 10:
            msg = "(づ￣ ³￣)づ{} ⊂(´・ω・｀⊂)".format(name)
        await ctx.send(msg)

    @commands.command()
    @commands.guild_only()
    async def serverinfo(self, ctx):
        """Shows server's informations"""
        guild = ctx.guild
        online = len([m.status for m in guild.members if m.status != discord.Status.offline])
        total_users = len(guild.members)
        text_channels = len(guild.text_channels)
        voice_channels = len(guild.voice_channels)
        passed = (ctx.message.created_at - guild.created_at).days
        created_at = _("Since {}. That's over {} days ago!").format(
            guild.created_at.strftime("%d %b %Y %H:%M"), passed
        )
        data = discord.Embed(description=created_at, colour=(await ctx.embed_colour()))
        data.add_field(name=_("Region"), value=str(guild.region))
        data.add_field(name=_("Users"), value="{}/{}".format(online, total_users))
        data.add_field(name=_("Text Channels"), value=text_channels)
        data.add_field(name=_("Voice Channels"), value=voice_channels)
        data.add_field(name=_("Roles"), value=len(guild.roles))
        data.add_field(name=_("Owner"), value=str(guild.owner))
        data.set_footer(text=_("Server ID: ") + str(guild.id))

        if guild.icon_url:
            data.set_author(name=guild.name, url=guild.icon_url)
            data.set_thumbnail(url=guild.icon_url)
        else:
            data.set_author(name=guild.name)

        try:
            await ctx.send(embed=data)
        except discord.HTTPException:
            await ctx.send(_("I need the `Embed links` permission to send this."))

    @commands.command()
    async def urban(self, ctx, *, word):
        """Searches urban dictionary entries using the unofficial api"""

        try:
            url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()

            headers = {"content-type": "application/json"}

            async with aiohttp.ClientSession() as session:
                async with session.get(url, headers=headers) as response:
                    data = await response.json()

        except:
            await ctx.send(
                _("No Urban dictionary entries were found or there was an error in the process")
            )

        if data.get("error") != 404:

            if await ctx.embed_requested():
                # a list of embeds
                embeds = []
                for ud in data["list"]:
                    embed = discord.Embed()
                    embed.title = _("{} by {}").format(ud["word"].capitalize(), ud["author"])
                    embed.url = ud["permalink"]

                    description = "{} \n \n **Example : ** {}".format(
                        ud["definition"], ud.get("example", "N/A")
                    )
                    if len(description) > 2048:
                        description = "{}...".format(description[:2045])
                    embed.description = description

                    embed.set_footer(
                        text=_("{} Down / {} Up , Powered by urban dictionary").format(
                            ud["thumbs_down"], ud["thumbs_up"]
                        )
                    )
                    embeds.append(embed)

                if embeds is not None and len(embeds) > 0:
                    await menu(
                        ctx,
                        pages=embeds,
                        controls=DEFAULT_CONTROLS,
                        message=None,
                        page=0,
                        timeout=30,
                    )
            else:
                messages = []
                for ud in data["list"]:
                    description = _("{} \n \n **Example : ** {}").format(
                        ud["definition"], ud.get("example", "N/A")
                    )
                    if len(description) > 2048:
                        description = "{}...".format(description[:2045])
                    description = description

                    message = _(
                        "<{}> \n {} by {} \n \n {} \n \n {} Down / {} Up, Powered by urban "
                        "dictionary"
                    ).format(
                        ud["permalink"],
                        ud["word"].capitalize(),
                        ud["author"],
                        description,
                        ud["thumbs_down"],
                        ud["thumbs_up"],
                    )
                    messages.append(message)

                if messages is not None and len(messages) > 0:
                    await menu(
                        ctx,
                        pages=messages,
                        controls=DEFAULT_CONTROLS,
                        message=None,
                        page=0,
                        timeout=30,
                    )
        else:
            await ctx.send(
                _("No Urban dictionary entries were found or there was an error in the process")
            )
            return
