""" Discord API snake bot """

"""
MIT License

Copyright (c) 2016 AnonymousDapper

Permission is hereby granted
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import discord
import asyncio
import os
import logging
import sys
import functools
import traceback
import aiohttp
import json
import subprocess

from bs4 import BeautifulSoup as bs4
from datetime import datetime
from contextlib import contextmanager
from io import StringIO
from inspect import isawaitable

from cogs.utils import time, checks
from cogs.utils.colors import paint, back, attr
from cogs.utils import sql
from cogs.utils import permissions

from cogs.utils.boxy import Boxy

from discord.ext import commands

try:
    import uvloop
except ModuleNotFoundError:
    print("Can't find uvloop, defaulting to standard event loop")
else:
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    print("Using uvloop policy")

class Builtin:
    def __init__(self, bot):
        self.bot = bot

    @commands.command(name="quit", brief="exit")
    @checks.is_owner()
    async def quit_command(self, ctx):
        asyncio.ensure_future(self.bot.aio_session.close())
        await self.bot.logout()

    @commands.group(name="cog", invoke_without_command=True, brief="manage cogs")
    @checks.is_owner()
    async def manage_cogs(self, ctx, name:str, action:str):
        name = name.lower()
        action = action.lower()
        cog_name = "cogs.command_" + name

        if action == "load":
            if self.bot.extensions.get(cog_name) is not None:
                await ctx.send(f"Cog `{name}` is already loaded")
            else:
                try:
                    self.bot.load_extension(cog_name)
                except Exception as e:
                    await ctx.send(f"Failed to load `{name}`: [{type(e).__name__}]: {e}")
                    return

                finally:
                    await ctx.send(f"Loaded `{name}`")

        elif action == "unload":
            if self.bot.extensions.get(cog_name) is None:
                await ctx.send(f"Cog `{name}` is not loaded")
            else:
                try:
                    self.bot.unload_extension(cog_name)
                except Exception as e:
                    await ctx.send(f"Failed to unload `{name}`: [{type(e).__name__}]: {e}")
                    return

                finally:
                    await ctx.send(f"Unloaded `{name}`")

        elif action == "reload":
            if self.bot.extensions.get(cog_name) is None:
                await ctx.send(f"Cog `{name}` is not loaded")
            else:
                try:
                    self.bot.unload_extension(cog_name)
                    self.bot.load_extension(cog_name)
                except Exception as e:
                    await ctx.send(f"Failed to reload `{name}`: [{type(e).__name__}]: {e}")
                    return

                finally:
                    await ctx.send(f"Reloaded `{name}`")

    @manage_cogs.command(name="list", brief="list cogs")
    @checks.is_owner()
    async def list_cogs(self, ctx, name:str = None):
        if name is None:
            await ctx.send(f"Currently loaded cogs:\n{' '.join('`' + cog_name + '`' for cog_name in self.bot.extensions)}" if len(self.bot.extensions) > 0 else "No cogs loaded")
        else:
            cog_name = "cogs.command_" + name
            await ctx.send(f"`{name}` {'is not' if self.bot.extensions.get(cog_name) is None else 'is'} loaded")

    @commands.command(name="about", brief="some basic info")
    async def about_command(self, ctx):
        result = await self.bot.loop.run_in_executor(None, functools.partial(subprocess.run, 'git log --pretty=format:"%h by %an %ar (%s)" -n 1', stdout=subprocess.PIPE, shell=True, universal_newlines=True))

        await ctx.send(f"```md\n# Snake Bot Info\n* Discord.py {discord.version_info.major}.{discord.version_info.minor}.{discord.version_info.micro}\n* Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}\n* Latest Commit {result.stdout}\n```")

class SnakeBot(commands.Bot):
    @contextmanager
    def db_scope(self): # context manager for database sessions
        session = self.db.Session()
        try:
            yield session
            session.commit()
        except:
            traceback.print_exc()
            session.rollback()
        finally:
            session.close()

    @contextmanager
    def stdout_scope(self): # basically same idea, but for stdout
        old = sys.stdout
        new = StringIO()
        sys.stdout = new
 
       yield new
        sys.stdout = old

    @contextmanager
    def build_box(self, str_list, **kwargs):
        self.boxer.update(**kwargs)
        box = self.boxer(str_list)
        yield box
        self.boxer.reset()

    def _read_config(self, filename):
        with open(filename, "r", encoding="utf-8") as cfg:
            return json.load(cfg)

    def
 __init__(self, *args, **kwargs):
        self.log = logging.getLogger()
        self.log.setLevel(logging.INFO)
        self.log.addHandler(
            logging.FileHandler(filename="snake.log", encoding="utf-8", mode="w")
        )

        self._DEBUG = any("debug" == arg.lower() for arg in sys.argv)
        self.loop = asyncio.get_event_loop()
        self.permissions = permissions.Permissions
        self.permissions.bot = self
        self.config = self._read_config("config.json")
        self.invite_url = discord.utils.oauth_url("181584771510566922", permissions=discord.Permissions(permissions=8))

        credentials = self._read_config("credentials.json")
        self.token = credentials["token"]

        self.boxer = Boxy()

        self.newline = "\n"
        self.author_ids = [163521874872107009, 190966952649293824]

        super().__init__(
            *args,
            **kwargs,
            description="\nHsss!\n",
            help_attrs=dict(hidden=True),
            command_not_found="\N{WARNING SIGN} Whoops, '{}' doesn't exist!",
            command_has_no_subcommand="\N{WARNING SIGN} Sorry, '{1}' isn't part of '{0.name}'",
            command_prefix=self.get_prefix
        )

        self.aio_session = aiohttp.ClientSession()
        self.db = sql.SQL(db_name="snake", db_username=os.environ.get("SNAKE_DB_USERNAME"), db_password=os.environ.get("SNAKE_DB_PASSWORD"))
        self.boot_time = datetime.now()

        for filename in os.listdir("cogs"):
            if os.path.isfile("cogs/" + filename) and filename.startswith("command_"):
                name = filename[8:-3]
                cog_name = "cogs.command_" + name
                try:
                    self.load_extension(cog_name)
                except Exception as e:
                    print(f"Failed to load {paint(name, 'b_red')}: [{type(e).__name__}]: {e}")

    async def log_message(self, message, action):
        author = message.author
        channel = message.channel
        guild = channel.guild

        with self.db_scope() as session:
            msg_author = session.query(sql.User).filter_by(id=author.id).first()

            if msg_author is None:
                msg_author = sql.User(
                    id=author.id,
                    name=author.name,
                    bot=author.bot,
                    di