"""Base class for module, never use directly !!!"""
import os
import pickle
import zipfile

import discord
from packaging import version


class Storage:
    def __init__(self, base_path, client):
        self.client = client
        self.base_path = base_path
        try:
            os.makedirs(base_path)
        except FileExistsError:
            self.client.info("Le dossier {dossier} a déjà été créé.".format(dossier=self.base_path))

    def mkdir(self, directory):
        try:
            os.makedirs(self.path(directory))
        except FileExistsError:
            self.client.info("Le dossier {dossier} a déjà été créé.".format(dossier=directory))

    def mkzip(self, files, name):
        with zipfile.ZipFile(self.path(files), 'w', zipfile.ZIP_DEFLATED) as zip_file:
            for file in files:
                zip_file.write(self.path(file), compress_type=zipfile.ZIP_DEFLATED)
        return name

    def open(self, filename, *args, **kwargs):
        return open(self.path(filename), *args, **kwargs)

    def path(self, filename):
        return os.path.join(self.base_path, filename)

    def exists(self, filename):
        return os.path.exists(self.path(filename))


class BaseClass:
    """Base class for all modules, Override it to make submodules"""
    name = ""
    help = {
        "description": "",
        "commands": {

        }
    }
    help_active = False
    color = 0x000000
    command_text = None
    authorized_roles = []
    version = version.parse("0.0.1")
    min_base_version = version.parse("0.0.1")
    max_base_version = "0.0.1"

    def __init__(self, client):
        """Initialize module class

        Initialize module class, always call it to set self.client when you override it.

        :param client: client instance
        :type client: NikolaTesla"""
        self.client = client
        if not os.path.isdir(os.path.join("storage", self.name)):
            os.makedirs(os.path.join("storage", self.name))
        self.storage = Storage(os.path.join(self.client.base_path, self.name), client)

    async def send_help(self, channel):
        embed = discord.Embed(
            title="[{nom}] - Aide".format(nom=self.name),
            description=self.help["description"].format(prefix=self.client.config['prefix']),
            color=self.color
        )
        for command, description in self.help["commands"].items():
            embed.add_field(name=command.format(prefix=self.client.config['prefix'], command=self.command_text),
                            value=description.format(prefix=self.client.config['prefix'], command=self.command_text),
                            inline=False)
        await channel.send(embed=embed)

    async def auth(self, user, role_list):
        if type(role_list) == list:
            if user.id in self.client.owners:
                return True
            for guild in self.client.guilds:
                if guild.get_member(user.id):
                    for role_id in role_list:
                        if role_id in [r.id for r in guild.get_member(user.id).roles]:
                            return True
        elif type(role_list) == str:
            module_name = role_list
            if user.id in self.client.owners:
                return True
            authorized_roles = self.client.modules[module_name]["class"].authorized_roles
            if len(authorized_roles):
                for guild in self.client.guilds:
                    if guild.get_member(user.id):
                        for role_id in authorized_roles:
                            if role_id in [r.id for r in guild.get_member(user.id).roles]:
                                return True
            else:
                return True
            return False

    async def parse_command(self, message):
        """Parse a command_text from received message and execute function
        %git update
        com_update(m..)
        Parse message like `{prefix}{command_text} subcommand` and call class method `com_{subcommand}`.

        :param message: message to parse
        :type message: discord.Message"""
        if message.content.startswith(self.client.config["prefix"] + (self.command_text if self.command_text else "")):

            content = message.content.lstrip(
                self.client.config["prefix"] + (self.command_text if self.command_text else ""))
            sub_command, args, kwargs = self._parse_command_content(content)
            sub_command = "com_" + sub_command
            if sub_command in dir(self):
                await self.__getattribute__(sub_command)(message, args, kwargs)
            else:
                await self.command(message, [sub_command[4:]] + args, kwargs)

    @staticmethod
    def _parse_command_content(content):
        """Parse string

        Parse string like `subcommand argument "argument with spaces" -o -shortwaytopassoncharacteroption --longoption
        -o "option with argument"`. You can override this function to change parsing.

        :param content: content to parse
        :type content: str

        :return: parsed arguments: [subcommand, [arg1, arg2, ...], [(option1, arg1), (option2, arg2), ...]]
        :rtype: list[str, list, list]"""
        if not len(content.split()):
            return "", [], []
        # Sub_command
        sub_command = content.split()[0]
        args_ = []
        kwargs = []
        if len(content.split()) > 1:
            # Take the other part of command_text
            content = content.split(" ", 1)[1].replace("\"", "\"\"")
            # Splitting around quotes
            quotes = [element.split("\" ") for element in content.split(" \"")]
            # Split all sub chains but brute chains and flat the resulting list
            args = [item.split() if item[0] != "\"" else [item, ] for sublist in quotes for item in sublist]
            # Second plating
            args = [item for sublist in args for item in sublist]
            # args_ are arguments, kwargs are options with arguments
            i = 0
            while i < len(args):
                if args[i].startswith("\""):
                    args_.append(args[i][1:-1])
                elif args[i].startswith("--"):
                    if i + 1 >= len(args):
                        kwargs.append((args[i].lstrip("-"), None))
                        break
                    if args[i + 1][0] != "-":
                        kwargs.append((args[i].lstrip("-"), args[i + 1].strip("\"")))
                        i += 1
                    else:
                        kwargs.append((args[i].lstrip("-"), None))
                elif args[i].startswith("-"):
                    if len(args[i]) == 2:
                        if i + 1 >= len(args):
                            break
                        if args[i + 1][0] != "-":
                            kwargs.append((args[i].lstrip("-"), args[i + 1].strip("\"")))
                            i += 1
                        else:
                            kwargs.append((args[i].lstrip("-"), None))
                    else:
                        kwargs.extend([(arg, None) for arg in args[i][1:]])
                else:
                    args_.append(args[i])
                i += 1
        return sub_command, args_, kwargs

    async def _on_message(self, message):
        """Override this function to deactivate command_text parsing"""
        await self.parse_command(message)
        await self.on_message(message)

    async def command(self, message, args, kwargs):
        """Override this function to handle all messages starting with `{prefix}{command_text}`

        Function which is executed for all command_text doesn't match with a `com_{subcommand}` function"""
        pass

    def save_object(self, object_instance, object_name):
        """Save object into pickle file"""
        with self.storage.open(object_name, "wb") as f:
            pickler = pickle.Pickler(f)
            pickler.dump(object_instance)

    def load_object(self, object_name):
        """Load object from pickle file"""
        if self.save_exists(object_name):
            with self.storage.open(object_name, "rb") as f:
                unpickler = pickle.Unpickler(f)
                return unpickler.load()

    def save_exists(self, object_name):
        """Check if pickle file exists"""
        return self.storage.exists(object_name)

    def on_load(self):
        """This function is called when module is loaded"""
        pass

    async def on_socket_raw_receive(self, message):
        """Override this function to handle this event."""
        pass

    async def on_socket_raw_send(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_typing(self, channel, user, when):
        """Override this function to handle this event."""
        pass

    async def on_message(self, message):
        """Override this function to handle this event."""
        pass

    async def on_message_delete(self, message):
        """Override this function to handle this event."""
        pass

    async def on_raw_message_delete(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_raw_bulk_message_delete(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_message_edit(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_raw_message_edit(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_reaction_add(self, reaction, user):
        """Override this function to handle this event."""
        pass

    async def on_raw_reaction_add(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_reaction_remove(self, reaction, user):
        """Override this function to handle this event."""
        pass

    async def on_raw_reaction_remove(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_reaction_clear(self, message, reactions):
        """Override this function to handle this event."""
        pass

    async def on_raw_reaction_clear(self, payload):
        """Override this function to handle this event."""
        pass

    async def on_private_channel_delete(self, channel):
        """Override this function to handle this event."""
        pass

    async def on_private_channel_create(self, channel):
        """Override this function to handle this event."""
        pass

    async def on_private_channel_update(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_private_channel_pins_update(self, channel, last_pin):
        """Override this function to handle this event."""
        pass

    async def on_guild_channel_delete(self, channel):
        """Override this function to handle this event."""
        pass

    async def on_guild_channel_create(self, channel):
        """Override this function to handle this event."""
        pass

    async def on_guild_channel_update(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_guild_channel_pins_update(self, channel, last_pin):
        """Override this function to handle this event."""
        pass

    async def on_member_join(self, member):
        """Override this function to handle this event."""
        pass

    async def on_member_remove(self, member):
        """Override this function to handle this event."""
        pass

    async def on_member_update(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_guild_join(self, guild):
        """Override this function to handle this event."""
        pass

    async def on_guild_remove(self, guild):
        """Override this function to handle this event."""
        pass

    async def on_guild_update(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_guild_role_create(self, role):
        """Override this function to handle this event."""
        pass

    async def on_guild_role_delete(self, role):
        """Override this function to handle this event."""
        pass

    async def on_guild_role_update(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_guild_emojis_update(self, guild, before, after):
        """Override this function to handle this event."""
        pass

    async def on_guild_available(self, guild):
        """Override this function to handle this event."""
        pass

    async def on_guild_unavailable(self, guild):
        """Override this function to handle this event."""
        pass

    async def on_voice_state_update(self, member, before, after):
        """Override this function to handle this event."""
        pass

    async def on_member_ban(self, guild, user):
        """Override this function to handle this event."""
        pass

    async def on_member_unban(self, guild, user):
        """Override this function to handle this event."""
        pass

    async def on_group_join(self, channel, user):
        """Override this function to handle this event."""
        pass

    async def on_group_remove(self, channel, user):
        """Override this function to handle this event."""
        pass

    async def on_relationship_add(self, relationship):
        """Override this function to handle this event."""
        pass

    async def on_relationship_remove(self, relationship):
        """Override this function to handle this event."""
        pass

    async def on_relationship_update(self, before, after):
        """Override this function to handle this event."""
        pass

    async def on_ready(self):
        """Override this function to handle this event."""
        pass

    async def on_connect(self):
        """Override this function to handle this event."""
        pass

    async def on_shard_ready(self):
        """Override this function to handle this event."""
        pass

    async def on_resumed(self):
        """Override this function to handle this event."""
        pass

    async def on_error(self, event, *args, **kwargs):
        """Override this function to handle this event."""
        pass

    async def on_guild_integrations_update(self, guild):
        """Override this function to handle this event."""
        pass

    async def on_webhooks_update(self, channel):
        """Override this function to handle this event."""
        pass
