import asyncio
from typing import Union
import datetime
import time

from discord.ext import commands
import yaml


from cogs import checks
import database
import utils

# Loads the repeating interval dictionary
with open("conversions.yml", "r") as conversion_file:
    conversion_dict = yaml.load(conversion_file, Loader=yaml.Loader)


prefix = utils.get_prefix()


class Remind(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.reminders = []
        self.tasks = []
        asyncio.create_task(self.update_schedule())

    async def update_schedule(self):
        """Updates the schedule"""
        reminders = database.get_reminders()
        new_reminders = []
        for reminder in reminders:
            if reminder["date"] - time.time() < 0:
                database.remove_reminder(reminder)
            else:
                new_reminders.append(reminder)
            self.reminders.clear()
            self.reminders.extend(new_reminders)

    async def setup_reminders(self):
        """Sets up the reminders"""
        await self.clear_tasks()
        await self.update_schedule()
        scheduled_reminders = []
        for task in self.tasks:
            if task.get_coro().cr_frame is not None:
                scheduled_reminders.append(
                    task.get_coro().cr_frame.f_locals["reminder"]
                )
        # Create tasks for all reminders, call the remind function
        for reminder in self.reminders:
            if reminder not in scheduled_reminders:
                task = asyncio.create_task(self.remind(reminder))
                self.tasks.append(task)
                scheduled_reminders.append(
                    task.get_coro().cr_frame.f_locals["reminder"]
                )
        # Run the tasks
        asyncio.gather(*self.tasks)

    async def clear_tasks(self):
        for task in self.tasks:
            if task._state == "FINISHED":
                self.tasks.remove(task)

    async def remind(self, reminder: dict):
        """Execute one reminder"""
        # Check if the reminder is in the future and if it exists in the database
        if reminder["date"] > time.time() and database.get_reminders(**reminder) != []:
            await asyncio.sleep(reminder["date"] - time.time())
            # Checks if the reminder is still exists, in case of deletion
            if database.get_reminders(**reminder) != [] and reminder in self.reminders:
                await self.bot.get_channel(reminder["channel"]).send(
                    f"Reminder:\n{reminder['reminder_text']}"
                )
                if reminder["repeating"] != False:
                    await self.schedule_repeat(reminder)
                self.reminders.remove(reminder)
            # Remove the reminder
            database.remove_reminder(reminder)
        # Remove a reminder that has passed
        else:
            database.remove_reminder(reminder)

    async def schedule_repeat(self, reminder: dict):
        """Schedules a repeating reminder"""
        if reminder["repeating"] and database.get_reminders(**reminder) != []:
            # Calculate when the next reminder should be
            reminder_date = datetime.datetime.fromtimestamp(
                reminder["date"] + conversion_dict[reminder["repeating"]]
            )
            # Remove the old reminder
            database.remove_reminder(reminder)
            # Add the new reminder
            database.insert_reminder(
                reminder["guild"],
                reminder["channel"],
                reminder_date.year,
                reminder_date.month,
                reminder_date.day,
                reminder_date.hour,
                reminder_date.minute,
                reminder["reminder_text"],
                reminder["repeating"],
            )
            asyncio.create_task(self.setup_reminders())

    @commands.command(
        help="Date should be in month/day/year format, either with slashes or dashes (ex. month/day/year or month-day-year)\n\nRepeating is an interval of time after which the reminder should be sent again, must be either daily, weekly, biweekly, or triweekly\n\nText is the text the reminder will be sent with, wrap with quotations if this contains whitespace",
        aliases=["reminder", "add_r", "ar"],
    )
    @commands.check(checks.is_operator)
    async def add_reminder(
        self,
        ctx,
        date: str,
        user_time: str,
        text: str,
        repeating: Union[str, bool] = False,
    ):
        """Attempts to add a reminder"""
        # Checks if the reminder should repeat, and if it is a valid interval
        try:
            _date = utils.split_date(date)
            _time = utils.split_time(user_time)
        except UnboundLocalError:
            raise commands.UserInputError("Date or time was not in the correct format.")
        if repeating and repeating not in conversion_dict:
            raise commands.UserInputError()
        # Tries to insert the reminder
        result = database.insert_reminder(
            ctx.guild.id,
            ctx.channel.id,
            _date["year"],
            _date["month"],
            _date["day"],
            _time["hour"],
            _time["minute"],
            text,
            repeating,
        )
        # Sends a status message, and restarts the reminders
        if result:
            await asyncio.create_task(self.setup_reminders())
            await ctx.send(
                embed=utils.generate_embed(
                    "Reminder Stored",
                    f"{date}\n{user_time}\n{text}\nrepeating: {repeating}",
                )
            )
        # This means the insertion of the reminder failed
        else:
            await ctx.send(
                embed=utils.generate_embed(
                    "Error",
                    "`This reminder already exists in the database or is not in the future`",
                )
            )

    @add_reminder.error
    async def add_reminder_error(self, ctx, error):
        """Called when add_reminder() errors"""
        print(error)
        if isinstance(error, commands.errors.MissingRequiredArgument):
            await ctx.send(
                embed=utils.generate_embed(
                    "Error", f"`{error} Run {prefix}help add_reminder`"
                )
            )
        elif isinstance(error, commands.errors.UserInputError):
            await ctx.send(
                embed=utils.generate_embed(
                    "Error", f"`{error} Run {prefix}help add_reminder`"
                )
            )
        elif isinstance(error, commands.errors.CheckFailure):
            await ctx.send(
                embed=utils.generate_embed(
                    "Error", "`You do not have permissions for this command`"
                )
            )
        else:
            await ctx.send(
                embed=utils.generate_embed(
                    "Error",
                    f"`An unexpected error has occured, run {prefix}help add_reminder`",
                )
            )


def setup(bot):
    cog = Remind(bot)
    bot.add_cog(cog)
    asyncio.create_task(cog.setup_reminders())

[((19, 9, 19, 27), 'utils.get_prefix', 'utils.get_prefix', ({}, {}), '()', False, 'import utils\n'), ((16, 22, 16, 68), 'yaml.load', 'yaml.load', (), '', False, 'import yaml\n'), ((109, 5, 112, 5), 'discord.ext.commands.command', 'commands.command', (), '', False, 'from discord.ext import commands\n'), ((113, 5, 113, 39), 'discord.ext.commands.check', 'commands.check', ({(113, 20, 113, 38): 'checks.is_operator'}, {}), '(checks.is_operator)', False, 'from discord.ext import commands\n'), ((31, 20, 31, 44), 'database.get_reminders', 'database.get_reminders', ({}, {}), '()', False, 'import database\n'), ((60, 8, 60, 35), 'asyncio.gather', 'asyncio.gather', ({(60, 23, 60, 34): '*self.tasks'}, {}), '(*self.tasks)', False, 'import asyncio\n'), ((132, 17, 142, 9), 'database.insert_reminder', 'database.insert_reminder', ({(133, 12, 133, 24): 'ctx.guild.id', (134, 12, 134, 26): 'ctx.channel.id', (135, 12, 135, 25): "_date['year']", (136, 12, 136, 26): "_date['month']", (137, 12, 137, 24): "_date['day']", (138, 12, 138, 25): "_time['hour']", (139, 12, 139, 27): "_time['minute']", (140, 12, 140, 16): 'text', (141, 12, 141, 21): 'repeating'}, {}), "(ctx.guild.id, ctx.channel.id, _date['year'], _date\n    ['month'], _date['day'], _time['hour'], _time['minute'], text, repeating)", False, 'import database\n'), ((81, 12, 81, 46), 'database.remove_reminder', 'database.remove_reminder', ({(81, 37, 81, 45): 'reminder'}, {}), '(reminder)', False, 'import database\n'), ((84, 12, 84, 46), 'database.remove_reminder', 'database.remove_reminder', ({(84, 37, 84, 45): 'reminder'}, {}), '(reminder)', False, 'import database\n'), ((90, 28, 92, 13), 'datetime.datetime.fromtimestamp', 'datetime.datetime.fromtimestamp', ({(91, 16, 91, 73): "reminder['date'] + conversion_dict[reminder['repeating']]"}, {}), "(reminder['date'] + conversion_dict[reminder\n    ['repeating']])", False, 'import datetime\n'), ((94, 12, 94, 46), 'database.remove_reminder', 'database.remove_reminder', ({(94, 37, 94, 45): 'reminder'}, {}), '(reminder)', False, 'import database\n'), ((96, 12, 106, 13), 'database.insert_reminder', 'database.insert_reminder', ({(97, 16, 97, 33): "reminder['guild']", (98, 16, 98, 35): "reminder['channel']", (99, 16, 99, 34): 'reminder_date.year', (100, 16, 100, 35): 'reminder_date.month', (101, 16, 101, 33): 'reminder_date.day', (102, 16, 102, 34): 'reminder_date.hour', (103, 16, 103, 36): 'reminder_date.minute', (104, 16, 104, 41): "reminder['reminder_text']", (105, 16, 105, 37): "reminder['repeating']"}, {}), "(reminder['guild'], reminder['channel'],\n    reminder_date.year, reminder_date.month, reminder_date.day,\n    reminder_date.hour, reminder_date.minute, reminder['reminder_text'],\n    reminder['repeating'])", False, 'import database\n'), ((125, 20, 125, 42), 'utils.split_date', 'utils.split_date', ({(125, 37, 125, 41): 'date'}, {}), '(date)', False, 'import utils\n'), ((126, 20, 126, 47), 'utils.split_time', 'utils.split_time', ({(126, 37, 126, 46): 'user_time'}, {}), '(user_time)', False, 'import utils\n'), ((130, 18, 130, 43), 'discord.ext.commands.UserInputError', 'commands.UserInputError', ({}, {}), '()', False, 'from discord.ext import commands\n'), ((35, 16, 35, 50), 'database.remove_reminder', 'database.remove_reminder', ({(35, 41, 35, 49): 'reminder'}, {}), '(reminder)', False, 'import database\n'), ((70, 30, 70, 41), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((70, 46, 70, 80), 'database.get_reminders', 'database.get_reminders', ({}, {}), '(**reminder)', False, 'import database\n'), ((88, 37, 88, 71), 'database.get_reminders', 'database.get_reminders', ({}, {}), '(**reminder)', False, 'import database\n'), ((128, 18, 128, 88), 'discord.ext.commands.UserInputError', 'commands.UserInputError', ({(128, 42, 128, 87): '"""Date or time was not in the correct format."""'}, {}), "('Date or time was not in the correct format.')", False, 'from discord.ext import commands\n'), ((34, 34, 34, 45), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((73, 15, 73, 49), 'database.get_reminders', 'database.get_reminders', ({}, {}), '(**reminder)', False, 'import database\n'), ((71, 51, 71, 62), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((147, 22, 150, 17), 'utils.generate_embed', 'utils.generate_embed', ({(148, 20, 148, 37): '"""Reminder Stored"""', (149, 20, 149, 74): 'f"""{date}\n{user_time}\n{text}\nrepeating: {repeating}"""'}, {}), '(\'Reminder Stored\',\n    f"""{date}\n{user_time}\n{text}\nrepeating: {repeating}""")', False, 'import utils\n'), ((155, 22, 158, 17), 'utils.generate_embed', 'utils.generate_embed', ({(156, 20, 156, 27): '"""Error"""', (157, 20, 157, 92): '"""`This reminder already exists in the database or is not in the future`"""'}, {}), "('Error',\n    '`This reminder already exists in the database or is not in the future`')", False, 'import utils\n'), ((167, 22, 169, 17), 'utils.generate_embed', 'utils.generate_embed', ({(168, 20, 168, 27): '"""Error"""', (168, 29, 168, 71): 'f"""`{error} Run {prefix}help add_reminder`"""'}, {}), "('Error', f'`{error} Run {prefix}help add_reminder`')", False, 'import utils\n'), ((173, 22, 175, 17), 'utils.generate_embed', 'utils.generate_embed', ({(174, 20, 174, 27): '"""Error"""', (174, 29, 174, 71): 'f"""`{error} Run {prefix}help add_reminder`"""'}, {}), "('Error', f'`{error} Run {prefix}help add_reminder`')", False, 'import utils\n'), ((179, 22, 181, 17), 'utils.generate_embed', 'utils.generate_embed', ({(180, 20, 180, 27): '"""Error"""', (180, 29, 180, 77): '"""`You do not have permissions for this command`"""'}, {}), "('Error', '`You do not have permissions for this command`')", False, 'import utils\n'), ((185, 22, 188, 17), 'utils.generate_embed', 'utils.generate_embed', ({(186, 20, 186, 27): '"""Error"""', (187, 20, 187, 87): 'f"""`An unexpected error has occured, run {prefix}help add_reminder`"""'}, {}), "('Error',\n    f'`An unexpected error has occured, run {prefix}help add_reminder`')", False, 'import utils\n')]