from datetime import datetime, timedelta, timezone
from enum import Enum
from random import SystemRandom
from typing import Any, Dict, List, Optional, Tuple

from mlib.localization import tr

def _t(key: str, language: str = "en", **kwargs):
    return tr("bot.events.Halloween.translations." + key, "en-US", **kwargs)

import sqlalchemy as sa
from MFramework import (
    Attachment,
    Chance,
    Context,
    Embed,
    EventBetween,
    Groups,
    Snowflake,
    User,
    register,
)
from MFramework.commands.cooldowns import CacheCooldown, cooldown
from MFramework.database.alchemy.mixins import ServerID
from mlib.database import Base, Timestamp

from ...database.mixins import UserID
from ...database.types import HalloweenRaces as Race
from ...database.types import Statistic

# When | Where | Who | to Whom | to What | From what?

class HalloweenCooldown(CacheCooldown):
    current_faction: int = 0
    target_faction: int = 0
    remaining_factions: int = 0
    multipler: float = 1.0

    def __init__(
        self,
        ctx: Context,
        cooldown: timedelta,
        cooldown_type: str,
        func_args: Dict[str, Any],
        target_user: Optional[Snowflake] = None,
        *args,
        **kwargs,
    ) -> None:
        super().__init__(ctx=ctx, cooldown=cooldown, cooldown_type=cooldown_type)
        s = func_args.get("session", None)
        this_user = func_args.get("this_user", None)
        self.session = s or self.ctx.db.sql.session()
        # session.query(db.Inventory).filter(db.Inventory.user_id == ctx.user_id, db.Inventory.item_id in list(Race)).first()
        self.race = (
            this_user
            or self.session.query(Halloween.race)
            .filter(Halloween.server_id == ctx.guild_id, Halloween.user_id == ctx.user_id)
            .first()
            or Race.Human
        )
        self.get_race_counts(func_args.get("user", ctx.user).id)

    @property
    def cooldown(self) -> timedelta:
        c = (self._cooldown + self.cooldown_var) * self.multipler
        return c if c.total_seconds() > 300 else timedelta(minutes=5)

    @property
    def is_top_faction(self) -> bool:
        """Whether it's currently a top faction"""
        return (self.remaining_factions // 2) < self.current_faction

    @property
    def faction_difference(self) -> int:
        """Difference to other factions"""
        return self.current_faction - (self.remaining_factions // 2)

    @property
    def cooldown_var(self) -> timedelta:
        """Current Cooldown Variance based on difference to other factions"""
        difference = self.faction_difference
        if not self.is_top_faction:
            difference = difference * 2
        return timedelta(minutes=difference * 3)

    def get_race_counts(self, target_user_id: Snowflake) -> Tuple[int, int, int]:
        """Returns count for current user faction, user's target faction and collectively remaining factions"""
        user = Halloween.fetch_or_add(self.session, server_id=self.ctx.guild_id, user_id=self.ctx.user_id)
        total = Halloween.get_total(self.session, self.ctx.guild_id, user.race)
        if user.race not in IMMUNE_TABLE.keys():
            if user.race is Race.Human:
                return
            try:
                top = sorted(filter(lambda x: x[0] in HUNTERS, total.items()), key=lambda x: x[1])[-1]
            except:
                return
            top_hunted = total.get(CURE_TABLE[top[0]], 1)
            top_hunter = top[1]
            top_ratio = 1 / (top_hunted / top_hunter)
            current_faction = total.get(user.race, 1)
            currently_hunted = total.get(CURE_TABLE[user.race], 1)
            current_ratio = 1 / (currently_hunted / current_faction)
            self.multipler = 1 - (top_ratio - current_ratio)
            return
        self.current_faction = total.get(user.race, 1)

        target_user = Halloween.fetch_or_add(self.session, server_id=self.ctx.guild_id, user_id=target_user_id)
        # Halloween.get_total(session, self.ctx.guild_id, target_user.race)
        self.target_faction = total.get(target_user.race, 1)

        remaining_monsters = list(i for i in IMMUNE_TABLE.keys() if i not in (user.race, target_user.race))
        # Halloween.get_total(session, self.ctx.guild_id, remaining_monsters[0])
        self.remaining_factions = total.get(remaining_monsters[0], 1) + self.target_faction

from MFramework.commands.exceptions import Error

class HalloweenException(Error):
    _key = ""

    def __init__(self, key: str, language: str = "en", *args: object, **kwargs) -> None:
        value = _t(self._key + key, language, default=_t(self._key + "generic", language), **kwargs)
        super().__init__(value, *args)

class Cant(HalloweenException):
    """User has wrong state"""

    _key = "cant_"

class Failed(HalloweenException):
    """Failed to bite or cure"""

    _key = "failed_"

class Immune(HalloweenException):
    """Target is on the same side or is immune"""

    _key = "immune_"

class COOLDOWNS(Enum):
    DEFEND = timedelta(hours=1)
    CURE = timedelta(hours=2)
    BITE = timedelta(hours=3)
    BETRAY = timedelta(hours=4)

IMMUNE_TABLE = {Race.Vampire: Race.Hunter, Race.Werewolf: Race.Huntsmen, Race.Zombie: Race.Enchanter}

HUNTERS = IMMUNE_TABLE.values()
CURE_TABLE = {v: k for k, v in IMMUNE_TABLE.items()}

class DRINKS(Enum):
    Nightmare = "Random"  # Race.Human
    Wine = "Vampire"  # Race.Vampire
    Moonshine = "Werewolf"  # Race.Werewolf
    Vodka = "Zombie"  # Race.Zombie

class Guilds(Enum):
    Any = "Random"
    Dawnguards = "Hunter"  # Race.Hunter
    Rangers = "Huntsmen"  # Race.Huntsmen
    Inquisition = "Enchanter"  # Race.Enchanter

ROLES = {}

class HalloweenLog(ServerID, UserID, Timestamp, Base):
    timestamp: datetime = sa.Column(sa.TIMESTAMP(timezone=True), primary_key=True, server_default=sa.func.now())
    target_id: User = sa.Column(
        sa.ForeignKey("User.id", ondelete="Cascade", onupdate="Cascade"), primary_key=False, nullable=False
    )
    race: Race = sa.Column(sa.Enum(Race))
    previous: Race = sa.Column(sa.Enum(Race))

class Halloween(ServerID, UserID, Base):
    server_id: Snowflake = sa.Column(
        sa.ForeignKey("Server.id", ondelete="Cascade", onupdate="Cascade"), primary_key=True, nullable=False, default=0
    )
    user_id: Snowflake = sa.Column(
        sa.ForeignKey("User.id", ondelete="Cascade", onupdate="Cascade"), primary_key=True, nullable=False, default=0
    )
    race: Race = sa.Column(sa.Enum(Race), default=Race.Human)
    protected: datetime = sa.Column(sa.TIMESTAMP(timezone=True))

    @classmethod
    def fetch_or_add(
        cls: "Halloween", s: sa.orm.Session, user_id: Snowflake, server_id: Snowflake, **kwargs
    ) -> "Halloween":
        from ...database.models import User

        user = User.fetch_or_add(s, id=user_id)
        r = super().fetch_or_add(s, user_id=user_id, server_id=server_id, **kwargs)
        if r.race == None:
            r.race = Race.Human
            s.commit()
        return r

    @classmethod
    def get_total(cls, s: sa.orm.Session, guild_id: Snowflake, race: Race = None) -> Dict[Race, int]:
        q = s.query(sa.func.count(cls.race), cls.race).filter(cls.server_id == guild_id)
        if race:
            q.filter(cls.race == race)
        r = q.group_by(cls.race).all()  # .count()
        return {k: v for v, k in r}

    def turn_another(self, s: sa.orm.Session, target_user: Snowflake, race: Race = None) -> List[Race]:
        if self.race is Race.Human:
            if self.user_id == target_user:
                t = self
                if race in [i.name for i in HUNTERS]:
                    self.protected = datetime.now(tz=timezone.utc) + timedelta(hours=1)
                else:
                    self.protected = datetime.now(tz=timezone.utc) + timedelta(minutes=30)
        else:
            t = Halloween.fetch_or_add(s, server_id=self.server_id, user_id=target_user)

            if t.protected and (t.protected > datetime.now(tz=timezone.utc)):
                raise HalloweenException("error_protected")

            turn_count = (
                s.query(sa.func.count(HalloweenLog.target_id))
                .filter(
                    HalloweenLog.server_id == self.server_id,
                    HalloweenLog.target_id == target_user,
                    HalloweenLog.user_id != HalloweenLog.target_id,
                    HalloweenLog.previous != HalloweenLog.race,
                )
                .first()
            )

            if turn_count and SystemRandom().randint(0, 1000) <= (turn_count[0] if turn_count[0] < 1000 else 999):
                raise HalloweenException("error_self_defend")

        remaining = Halloween.get_total(s, self.server_id, t.race).get(t.race, 1)
        if (
            not race
            and (
                self.race in IMMUNE_TABLE
                and (t.race in IMMUNE_TABLE or IMMUNE_TABLE.get(self.race) == t.race or self.race == t.race)
            )
            or remaining == 1
        ):
            raise Immune("target")
        elif not race and self.race in HUNTERS and CURE_TABLE.get(self.race, None) != t.race:
            raise HalloweenException("error_cure", currentClass=CURE_TABLE.get(self.race))

        if race:
            pass
        elif self.race in HUNTERS:
            race = Race.Human
        elif self.race in IMMUNE_TABLE.keys():
            race = self.race

        previous = t.race
        s.add(
            HalloweenLog(
                server_id=self.server_id, user_id=self.user_id, target_id=target_user, race=race, previous=previous
            )
        )
        t.race = race
        s.commit()
        return previous, race

async def update_user_roles(ctx: Context, user_id: Snowflake, previous_race: Race, new_race: Race, s: sa.orm.Session):
    """Updates roles of user to reflect race change"""
    get_roles(ctx.guild_id, s)
    if type(previous_race) is Race or previous_race in list(i.name for i in Race):
        if type(previous_race) is Race:
            previous_race = previous_race.name
        p_role = ROLES.get(ctx.guild_id, {}).get(previous_race, None)
        if p_role:
            await ctx.bot.remove_guild_member_role(ctx.guild_id, user_id, p_role, "Halloween Minigame")
    if type(new_race) is Race or new_race in list(i.name for i in Race):
        if type(new_race) is Race:
            new_race = new_race.name
        role = ROLES.get(ctx.guild_id, {}).get(new_race, None)
        if role:
            await ctx.bot.add_guild_member_role(ctx.guild_id, user_id, role, "Halloween Minigame")

async def turn(
    ctx: Context,
    s: sa.orm.Session,
    this_user: Halloween,
    target_user_id: Snowflake,
    to_race: Race = None,
    action: str = None,
) -> str:
    """Turns target user into race of invoking user or provided race"""
    p, r = this_user.turn_another(s, target_user_id, to_race)
    await update_user_roles(ctx, target_user_id, p, r, s=s)
    return _t(
        f"success_{action}", ctx.language, author=ctx.user_id, target=target_user_id, currentClass=r, previousClass=p
    )

def get_total(total: List[Statistic]) -> Tuple[int, int]:
    """Returns total bites and population"""
    pass

# Factions
#   reinforce - send reinforcments (turn x% of users on that server into that faction) to another server - only for leading faction on a server!

@register(group=Groups.GLOBAL)
@EventBetween(after_month=10, after_day=14, before_month=11, before_day=7)
async def halloween(ctx: Context) -> Embed:
    """Halloween Event commands"""
    e = Embed()
    e.setTitle("Halloween Biting Minigame")
    e.addField("Monster Rules", _t("rules_main_monsters", language=ctx.language))
    e.addField("Hunter Rules", _t("rules_main_hunters", language=ctx.language))
    e.addField("\u200b", "\u200b")
    e.addField(
        name=_t("rules_drinking_title", language=ctx.language),
        value=(
            "- "
            + _t(
                "rules_drinking",
                language=ctx.language,
                drink_cmd="/halloween humans drink",
                drink_nightmare="Nightmare",
            )
            + "\n- "
            + _t("rules_enlisting", language=ctx.language, enlist_cmd="/halloween humans enlist")
        ),
    )
    e.addField(
        name=_t("rules_joining_title", language=ctx.language),
        value=_t(
            "rules_joining",
            language=ctx.language,
            drink_cmd="/halloween humans drink",
            enlist_cmd="/halloween humans enlist",
        ),
    )
    e.addField(
        name=_t("rules_last_protection_title", language=ctx.language),
        value=_t("rules_last_protection", language=ctx.language),
    )
    e.addField(
        name="Attributions",
        value="Minigame (Biting & Fear) was inspired by event in (now inactive) web browser game called Nightwood that appeared between 2010 and 2014"
        + "\nRole icons were contributed by Johnpaul Leon#0725",
    )
    return e

@register(group=Groups.ADMIN, main=halloween)
async def settings():
    """Shows settings for Halloween Event"""
    pass

def get_roles(guild_id, s):
    from MFramework.database.alchemy import Role
    from MFramework.database.alchemy.types import Setting

    if guild_id not in ROLES:
        ROLES[guild_id] = {}
        roles = s.query(Role).filter(Role.server_id == guild_id, Role.settings.any(name=Setting.Special)).all()
        for role in roles:
            ROLES[guild_id][role.get_setting(Setting.Special)] = role.id
    return ROLES[guild_id]

@register(group=Groups.ADMIN, main=settings)
async def roles(ctx: Context, delete: bool = False, update_permissions: bool = False):
    """Create and/or add roles related to Halloween Event
    Params
    ------
    delete:
        Delete Event roles (*Only ones that were made by bot)
    update_permissions:
        Sets permissions for commands to be only used by appropriate race
    """
    s = ctx.db.sql.session()
    roles = get_roles(ctx.guild_id, s)
    if roles:
        # if update_permissions:
        #    permissions = []
        #    commands = []
        #    for cmd in commands:
        #        command_permissions = []
        #        from MFramework import Application_Command_Permissions, Application_Command_Permission_Type, Guild_Application_Command_Permissions
        #        for role in roles: #TODO: get only roles according to faction by command
        #            command_permissions.append(Application_Command_Permissions(
        #                id=role,
        #                type=Application_Command_Permission_Type.ROLE,
        #                permission=True
        #            ))
        #        if command_permissions != []:
        #            permissions.append(Guild_Application_Command_Permissions(
        #                id = cmd.id,
        #                permissions=command_permissions
        #            ))
        #    await ctx.bot.batch_edit_application_command_permissions(ctx.bot.application.id, ctx.guild_id, permissions)

        users = s.query(Halloween).filter(Halloween.server_id == ctx.guild_id).all()
        for user in users:
            await ctx.bot.add_guild_member_role(
                ctx.guild_id, user.user_id, roles.get(user.race.name), "Halloween Minigame"
            )
    else:
        for race in Race:
            role = await ctx.bot.create_guild_role(
                ctx.guild_id,
                _t(race.name.lower().replace(" ", "_"), ctx.language, count=1).title(),
                0,
                reason="Created Role for Halloween Minigame",
            )
            from MFramework.database.alchemy import Role
            from MFramework.database.alchemy.types import Setting

            r = Role.fetch_or_add(s, server_id=ctx.guild_id, id=role.id)
            r.add_setting(Setting.Special, race.name)
        s.commit()

import functools

def inner(f, races: List[Race], main: object, should_register: bool = True):
    @functools.wraps(f)
    def wrapped(ctx: Context, s: sa.orm.Session = None, this_user: Halloween = None, **kwargs):
        s = s or ctx.db.sql.session()
        user = this_user or Halloween.fetch_or_add(s, server_id=ctx.guild_id, user_id=ctx.user_id)
        if user.race in races:
            return f(ctx=ctx, session=s, this_user=user, **kwargs)
        elif user.race is Race.Human:
            raise Error("You want to do what?")
        raise Cant(f.__name__)

    if should_register:
        register(group=Groups.GLOBAL, main=main)(
            EventBetween(after_month=10, after_day=14, before_month=11, before_day=7)(wrapped)
        )
    return wrapped

##########
# HUMANS #
##########

@register(group=Groups.GLOBAL, main=halloween)
def humans(cls=None, *, should_register: bool = True):
    """Commands related to humans"""
    i = functools.partial(
        inner,
        races=[Race.Human, Race.Vampire, Race.Werewolf, Race.Zombie],
        main=humans,
        should_register=should_register,
    )
    if cls:
        return i(cls)
    return i

@humans
async def enlist(ctx: Context, guild: Guilds, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Enlist as a hunter
    Params
    ------
    guild:
        Hunter's guild you want to join"""
    if this_user.race is not Race.Human and guild is not Guilds.Any:
        total = Halloween.get_total(session, ctx.guild_id)
        _race = Race.get(guild.value)
        if total.get(_race) > total.get(CURE_TABLE.get(_race)) // 3:
            raise Cant("enlist", ctx.language)
    if guild is Guilds.Any:
        total = sorted(Halloween.get_total(session, ctx.guild_id).items(), key=lambda x: x[1])
        if total:
            for race in total:
                if race[0] in HUNTERS:
                    guild = race[0]
                    break
        else:
            from random import SystemRandom

            guild = SystemRandom().choice([Race.Hunter, Race.Huntsmen, Race.Enchanter])
    return await turn(ctx, session, this_user, ctx.user_id, guild.value, action="enlist")

@humans
async def drink(ctx: Context, type: DRINKS, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Drink an unknown beverage to become a monster
    Params
    ------
    type:
        Drink you want to drink"""
    if this_user.race is not Race.Human:
        raise Cant("drink", ctx.language)
    previous = (
        session.query(HalloweenLog)
        .filter(HalloweenLog.server_id == ctx.guild_id, HalloweenLog.user_id == ctx.user_id)
        .first()
    )
    if previous and type is not DRINKS.Nightmare:
        return "Sadly, you can choose your poison only first time around as a freshblood. Nightmarish potion is your only option to drink now."

    if type is DRINKS.Nightmare:
        total = sorted(Halloween.get_total(session, ctx.guild_id).items(), key=lambda x: x[1])
        if total:
            for race in total:
                if race[0] in IMMUNE_TABLE:
                    type = race[0]
                    break
        else:
            from random import SystemRandom

            type = SystemRandom().choice([Race.Vampire, Race.Werewolf, Race.Zombie])
    return await turn(ctx, session, this_user, ctx.user_id, type.value, action="drink")

############
# MONSTERS #
############

@register(group=Groups.GLOBAL, main=halloween)
def monsters(cls=None, *, should_register: bool = True):
    """Commands related to Monster's factions"""
    i = functools.partial(inner, races=IMMUNE_TABLE.keys(), main=monsters, should_register=should_register)
    if cls:
        return i(cls)
    return i

@monsters
@cooldown(hours=3, logic=HalloweenCooldown)
async def bite(ctx: Context, target: User, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Bite your target to turn into one of your own kin
    Params
    ------
    target:
        Target you want to bite"""
    return await turn(ctx, session, this_user, target.id, action="bite")

###########
# HUNTERS #
###########

@register(group=Groups.GLOBAL, main=halloween)
def hunters(cls=None, *, should_register: bool = True):
    """Commands related to Hunter's factions"""
    i = functools.partial(inner, races=HUNTERS, main=hunters, should_register=should_register)
    if cls:
        return i(cls)
    return i

@hunters
@cooldown(hours=2, logic=HalloweenCooldown)
async def cure(ctx: Context, target: User, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Cure your target from the darkness back into human being
    Params
    ------
    target:
        Target you want to cure"""
    return await turn(ctx, session, this_user, target.id, action="cure")

@hunters
@cooldown(hours=1, logic=HalloweenCooldown)
async def defend(ctx: Context, target: User, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Protect fellow hunter from being bitten
    Params
    ------
    target:
        Target you want to protect"""
    if target.id == ctx.user_id:
        raise HalloweenException("error_defend", ctx.language)

    target_user = (
        session.query(Halloween).filter(Halloween.server_id == ctx.guild_id, Halloween.user_id == target.id).first()
    )

    if target_user.race not in HUNTERS:
        raise Failed("defend", ctx.language)

    now = datetime.now(tz=timezone.utc)
    if target_user.protected is None or target_user.protected < now:
        duration = SystemRandom().randint(5, 40)
        delta = now + timedelta(minutes=duration)
        target_user.protected = delta
        session.add(
            HalloweenLog(
                server_id=ctx.guild_id,
                target_id=target.id,
                previous=target_user.race,
                race=target_user.race,
                user_id=this_user.user_id,
            )
        )
        session.commit()
        return _t("success_defend", ctx.language, duration=duration, target=target.id)

    raise HalloweenException("error_protected", ctx.language)

@hunters
@cooldown(hours=4, logic=HalloweenCooldown)
@Chance(8, fail_message="Couldn't convince them to betray their kind.")
async def betray(ctx: Context, target: User, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Attempt to convince a monster you are hunting to join the cause and fight with the darkness instead
    Params
    ------
    target:
        Target you want to convince"""
    return await turn(ctx, session, this_user, target.id, this_user.race, action="betray")

@hunters
@cooldown(hours=2, logic=HalloweenCooldown)
@Chance(30, fail_message="Couldn't find any monster nearby!")
async def track(ctx: Context, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Attempt to track a monster's footprints
    Params
    ------
    target:
        Target you want to convince"""
    # NOTE: Alternative would be to also check monsters this hunter hunts for
    # TODO: Weighted chance, prioritize users that are inactive/have less xp/were active long ago
    r = (
        session.query(Halloween)
        .filter(Halloween.race.in_(list(IMMUNE_TABLE.keys())), Halloween.server_id == ctx.guild_id)
        .order_by(sa.func.random())
        .limit(1)
        .first()
    )
    return f"You find signs of struggle. Investigating spilled blood leads you to a {r.race.name}: <@{r.user_id}>"

@hunters
@cooldown(hours=2, logic=HalloweenCooldown)
@Chance(50, fail_message="You are trying to do WHAT?! KEEP FIGHTING!")
async def desert(ctx: Context, *, session: sa.orm.Session, this_user: Halloween) -> str:
    """Attempt to desert from being a hunter"""
    return await turn(ctx, session, this_user, ctx.user_id, Race.Human, action="desert")

# @register(group=Groups.GLOBAL, name="Defend or Betray")
@hunters(should_register=False)
async def defend_or_betray(ctx: Context, user: User, *, session: sa.orm.Session, this_user: Halloween):
    """
    Defend fellow hunter or convince monster depending on target's class
    """
    target_user = (
        session.query(Halloween).filter(Halloween.server_id == ctx.guild_id, Halloween.user_id == user.id).first()
    )
    if target_user.race in HUNTERS:
        return await defend(ctx, target=user, s=session, this_user=this_user)
    elif target_user.race is CURE_TABLE.get(this_user.race):
        return await betray(ctx, target=user, s=session, this_user=this_user)
    await ctx.deferred(private=True)
    raise Cant("generic", ctx.language)

# @register(group=Groups.GLOBAL, name="Bite or Cure")
async def bite_or_cure(ctx: Context, user: User):
    """
    Bite or cure user depending on your class
    """
    session = ctx.db.sql.session()
    this_user = (
        session.query(Halloween).filter(Halloween.server_id == ctx.guild_id, Halloween.user_id == ctx.user.id).first()
    )
    if this_user.race in IMMUNE_TABLE:
        return await bite(ctx, target=user, s=session, this_user=this_user)
    elif this_user.race in HUNTERS:
        return await cure(ctx, target=user, s=session, this_user=this_user)
    await ctx.deferred(private=True)
    raise Cant("generic", ctx.language)

@register(group=Groups.GLOBAL, main=halloween)
async def leaderboard(ctx: Context, user: User = None, limit: int = 10) -> Embed:
    """
    Shows Event leaderboard based on amount of turns
    Params
    ------
    user:
        Shows stats of another user
    limit:
        How many scores to show
    """
    s = ctx.db.sql.session()
    total_turned = (
        s.query(sa.func.count(HalloweenLog.user_id), HalloweenLog.user_id)
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
        )
        .group_by(HalloweenLog.user_id)
        .order_by(sa.func.count(HalloweenLog.user_id).desc())
        .limit(limit)
        .all()
    )
    # top_race_turns = s.query(sa.func.count(HalloweenLog.race), HalloweenLog.race).filter(HalloweenLog.server_id == ctx.guild_id).group_by(HalloweenLog.race).all()
    # turned_by_user = s.query(sa.func.count(HalloweenLog.race), HalloweenLog.race, HalloweenLog.user_id).filter(HalloweenLog.server_id == ctx.guild_id).group_by(HalloweenLog.user_id, HalloweenLog.race).all()
    if not any(user.id == i.user_id for i in total_turned):
        _user = (
            s.query(sa.func.count(HalloweenLog.user_id), HalloweenLog.user_id)
            .filter(
                HalloweenLog.server_id == ctx.guild_id,
                HalloweenLog.user_id == user.id,
                HalloweenLog.user_id != HalloweenLog.target_id,
                HalloweenLog.previous != HalloweenLog.race,
            )
            .group_by(HalloweenLog.user_id)
            .first()
        )
        if _user:
            total_turned.append(_user)
    from MFramework.utils.leaderboards import Leaderboard, Leaderboard_Entry

    entries = []
    for row in total_turned:
        entries.append(Leaderboard_Entry(ctx, row[1], row[0]))
    return [Leaderboard(ctx, user.id, entries, limit).as_embed()]

@register(group=Groups.GLOBAL, main=halloween)
async def history(ctx: Context, user: User = None, limit: int = 10) -> Embed:
    """
    Shows turn history
    Params
    ------
    user:
        User's history to show
    limit:
        How many turns to show
    """
    s = ctx.db.sql.session()
    from MFramework import Guild_Member

    turns = []
    turn_history = (
        s.query(HalloweenLog)
        .filter(HalloweenLog.server_id == ctx.guild_id, HalloweenLog.target_id == user.id)
        .order_by(HalloweenLog.timestamp.desc())
        .limit(limit)
        .all()
    )
    for entry in turn_history[: int(limit)]:
        _u = entry.user_id
        _u = ctx.cache.members.get(int(_u), Guild_Member(user=User(username=_u))).user.username
        if entry.previous == entry.race:
            line = "Defended"
        elif entry.user_id == entry.target_id:
            if entry.race in IMMUNE_TABLE:
                line = f"Drank potion and became `{entry.race}`"
            elif entry.race in HUNTERS:
                line = f"Enlisted as `{entry.race}`"
            else:
                line = f"Deserted from `{entry.previous}`"
        elif entry.previous in IMMUNE_TABLE and entry.race in HUNTERS:
            line = f"Convinced to fight as `{entry.race}`"
        else:
            line = f"`{entry.previous}` into `{entry.race}`"
        turns.append(f"[<t:{int(entry.timestamp.timestamp())}:d>] {line} by `{_u}`")

    targets = []
    targets_history = (
        s.query(HalloweenLog)
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id == user.id,
            HalloweenLog.user_id != HalloweenLog.target_id,
        )
        .order_by(HalloweenLog.timestamp.desc())
        .limit(limit)
        .all()
    )
    for entry in targets_history[: int(limit)]:
        _u = entry.target_id
        _u = ctx.cache.members.get(int(_u), Guild_Member(user=User(username=_u))).user.username
        if entry.previous == entry.race:
            line = "was protected from being turned"
        elif entry.previous in IMMUNE_TABLE and entry.race in HUNTERS:
            line = f"was convinced to enlist as `{entry.race}`"
        else:
            line = f"from `{entry.previous}` into `{entry.race}`"
        targets.append(f"[<t:{int(entry.timestamp.timestamp())}:d>] `{_u}` {line}")

    e = Embed().setTitle(f"{user.username}'s History")
    if turns:
        e.addField("Turns", "\n".join(turns))
    if targets:
        e.addField("Targets", "\n".join(targets))
    turn_count = (
        s.query(sa.func.count(HalloweenLog.target_id))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.target_id == user.id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
        )
        .first()
    )
    targets_count = (
        s.query(sa.func.count(HalloweenLog.user_id))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id == user.id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
        )
        .first()
    )
    e.addField("Statistics", f"Total Turns: `{turn_count[0]}`\nTotal Targets: `{targets_count[0]}`")
    return [e]

@register(group=Groups.GLOBAL, main=halloween)
async def statistics(ctx: Context) -> Embed:
    """
    Shows event related statistics
    """
    s = ctx.db.sql.session()
    e = Embed().set_title("Server Statistics")
    _total = sorted(Halloween.get_total(s, ctx.guild_id).items(), key=lambda x: x[1], reverse=True)
    total = "\n".join([f"`{i[0]:10}`: `{i[1]:>5}`" for i in _total])
    e.add_field("Members", total, True)
    turns_to = (
        s.query(sa.func.count(HalloweenLog.race), HalloweenLog.race)
        .filter(HalloweenLog.server_id == ctx.guild_id)
        .group_by(HalloweenLog.race)
        .order_by(sa.func.count(HalloweenLog.race).desc())
        .all()
    )
    turns_to = "\n".join([f"`{i[1]:10}`: `{i[0]:>5}`" for i in turns_to])
    e.add_field("Mostly Turned Into", turns_to, True)
    turns_from = (
        s.query(sa.func.count(HalloweenLog.previous), HalloweenLog.previous)
        .filter(HalloweenLog.server_id == ctx.guild_id)
        .group_by(HalloweenLog.previous)
        .order_by(sa.func.count(HalloweenLog.previous).desc())
        .all()
    )
    turns_from = "\n".join([f"`{i[1]:10}`: `{i[0]:>5}`" for i in turns_from])
    e.add_field("Mostly Turned From", turns_from, True)

    active_players = (
        s.query(sa.func.count(HalloweenLog.user_id))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.race != HalloweenLog.previous,
        )
        .group_by(HalloweenLog.user_id)
        .all()
    )
    total_players = (
        s.query(sa.func.count(HalloweenLog.user_id))
        .filter(HalloweenLog.server_id == ctx.guild_id)
        .group_by(HalloweenLog.user_id)
        .all()
    )
    e.set_footer(
        f"Active/Total/Participants: {len([i for i in active_players if i[0] > 1])}/{len(total_players)}/{sum([i[1] for i in _total])}"
    )
    return e

@register(group=Groups.GLOBAL, main=halloween)
async def cooldowns(ctx: Context):
    """
    Shows current faction cooldowns
    """
    s = ctx.db.sql.session()
    e = Embed().set_title("Current Cooldowns").set_footer("Illustrative only. Actual cooldowns might differ")
    t = Halloween.get_total(s, ctx.guild_id)

    def cooldown_var(race, a, b):
        diff = t.get(race) - ((t.get(a) + t.get(b)) // 2)
        if ((t.get(a) + t.get(b)) // 2) > t.get(race):
            diff *= 2
        return timedelta(minutes=diff * 3)

    def get(cooldown: COOLDOWNS, race: Race, other1: Race, other2: Race, multipler=1):
        c = (cooldown.value + cooldown_var(race, other1, other2)) * multipler
        return c if c.total_seconds() > 300 else timedelta(minutes=5)

    def ratio(hunter: Race):
        return 1 / (t.get(CURE_TABLE[hunter], 1) / t.get(hunter, 1))

    top = sorted(filter(lambda x: x[0] in HUNTERS, t.items()), key=lambda x: x[1], reverse=True)[0]
    r = 1 / (t.get(CURE_TABLE[top[0]], 1) / top[1])
    top = top[0]

    e.add_field(
        "Monsters",
        "\n".join(
            [
                f"Vampire ({t.get(Race.Vampire)}): {get(COOLDOWNS.BITE, Race.Vampire, Race.Werewolf, Race.Zombie)}",
                f"Werewolf ({t.get(Race.Werewolf)}): {get(COOLDOWNS.BITE, Race.Werewolf, Race.Vampire, Race.Zombie)}",
                f"Zombie ({t.get(Race.Zombie)}): {get(COOLDOWNS.BITE, Race.Zombie, Race.Werewolf, Race.Vampire)}",
            ]
        ),
    )
    e.add_field(
        f"Hunters ({t.get(Race.Hunter)})",
        "\n".join(
            [
                f"Cure: {get(COOLDOWNS.CURE, Race.Hunter, Race.Huntsmen, Race.Enchanter, 1 - (r - ratio(Race.Hunter)))}",
                f"Betray: {get(COOLDOWNS.BETRAY, Race.Hunter, Race.Huntsmen, Race.Enchanter, 1 - (r - ratio(Race.Hunter)))}",
                f"Defend: {get(COOLDOWNS.DEFEND, Race.Hunter, Race.Huntsmen, Race.Enchanter, 1 - (r - ratio(Race.Hunter)))}",
            ]
        ),
    )
    e.add_field(
        f"Huntsmen ({t.get(Race.Huntsmen)})",
        "\n".join(
            [
                f"Cure: {get(COOLDOWNS.CURE, Race.Huntsmen, Race.Hunter, Race.Enchanter, 1 - (r - ratio(Race.Huntsmen)))}",
                f"Betray: {get(COOLDOWNS.BETRAY, Race.Huntsmen, Race.Hunter, Race.Enchanter, 1 - (r - ratio(Race.Huntsmen)))}",
                f"Defend: {get(COOLDOWNS.DEFEND, Race.Huntsmen, Race.Hunter, Race.Enchanter, 1 - (r - ratio(Race.Huntsmen)))}",
            ]
        ),
    )
    e.add_field(
        f"Enchanters ({t.get(Race.Enchanter)})",
        "\n".join(
            [
                f"Cure: {get(COOLDOWNS.CURE, Race.Enchanter, Race.Huntsmen, Race.Hunter, 1 - (r - ratio(Race.Enchanter)))}",
                f"Betray: {get(COOLDOWNS.BETRAY, Race.Enchanter, Race.Huntsmen, Race.Hunter, 1 - (r - ratio(Race.Enchanter)))}",
                f"Defend: {get(COOLDOWNS.DEFEND, Race.Enchanter, Race.Huntsmen, Race.Hunter, 1 - (r - ratio(Race.Enchanter)))}",
            ]
        ),
    )
    return e

@register(group=Groups.GLOBAL, main=halloween, private_response=True)
async def info(ctx: Context, user: User = None):
    """
    Shows user's event info
    Params
    ------
    user:
        User whose statistics to show
    """
    s = ctx.db.sql.session()
    e = Embed().setTitle(user.username)
    cooldowns = []
    h_user = Halloween.fetch_or_add(s, server_id=ctx.guild_id, user_id=user.id)
    from mlib.localization import secondsToText as s2t

    for cooldown in COOLDOWNS:
        r = HalloweenCooldown(ctx, cooldown.value, cooldown.name.lower(), {"session": s, "this_user": h_user})
        if r.on_cooldown:
            cooldowns.append((r._type, s2t(int(r.remaining.total_seconds()))))
    if user.id == ctx.user_id and cooldowns:
        e.addField("Cooldowns", "\n".join(f"`{i[0].title()}`: `{i[1]}`" for i in cooldowns))
    e.addField("Currently is", h_user.race, True)
    now = datetime.now(tz=timezone.utc)
    if h_user.protected and h_user.protected > now:
        e.addField("Protected for", s2t(int((h_user.protected - now).total_seconds())), True)
    turn_count = (
        s.query(sa.func.count(HalloweenLog.target_id))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.target_id == user.id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
        )
        .first()
    )
    e.setFooter(f"Chance for self-defence: {turn_count[0]/10}%")
    return [e]

from MFramework import Bot, Guild_Member_Add, Message, onDispatch

@onDispatch
@EventBetween(after_month=10, after_day=14, before_month=11, before_day=7)
async def guild_member_add(self: Bot, data: Guild_Member_Add):
    s = self.db.sql.session()
    r = s.query(Halloween).filter(Halloween.user_id == data.user.id).first()
    if r and ROLES.get(data.guild_id, None):
        if type(r.race) is Race:
            race = r.race.name
        else:
            race = r.race
        role = ROLES.get(data.guild_id, {}).get(race, None)
        if role:
            await self.add_guild_member_role(data.guild_id, data.user.id, role, "Halloween Minigame")

CACHED_WEBHOOKS = {}

@onDispatch
@EventBetween(after_month=10, after_day=14, before_month=11, before_day=7)
async def message_create(self: Bot, data: Message):
    zombie_role = ROLES.get(data.guild_id, {}).get("Zombie", None)
    if zombie_role in data.member.roles:
        if SystemRandom().random() > (3 / 100):
            return
        if data.guild_id not in CACHED_WEBHOOKS:
            CACHED_WEBHOOKS[data.guild_id] = {}
        if not CACHED_WEBHOOKS.get(data.guild_id, {}):
            webhooks = await self.get_guild_webhooks(data.guild_id)
            for webhook in webhooks:
                if webhook.channel_id in CACHED_WEBHOOKS[data.guild_id]:
                    continue
                CACHED_WEBHOOKS[data.guild_id][webhook.channel_id] = (webhook.id, webhook.token)
        webhook = CACHED_WEBHOOKS[data.guild_id].get(data.channel_id, None)
        if not webhook:
            webhook = await self.create_webhook(data.channel_id, "Halloween", reason="Halloween Minigame")
            CACHED_WEBHOOKS[data.guild_id][data.channel_id] = (webhook.id, webhook.token)
            webhook = CACHED_WEBHOOKS[data.guild_id][data.channel_id]
        from MFramework import Allowed_Mentions

        try:
            await self.execute_webhook(
                webhook[0],
                webhook[1],
                wait=True,
                thread_id=getattr(data.thread, "id", None),
                content=data.content.replace(f"@everyone", "`@everyone`")
                + f" *...Br{'a'*SystemRandom().randint(1,5)}ins?*",
                username=data.member.nick or data.author.username,
                avatar_url=data.author.get_avatar(),
                allowed_mentions=Allowed_Mentions(parse=[]),
            )
        except:
            return
        await data.delete(reason="Halloween Minigame: Zombified message")

@register(group=Groups.OWNER, main=halloween, interaction=False)
async def summary(ctx: Context, include_humans: bool = False):
    """
    Shows event summary
    Params
    ------
    include_humans:
        Whether to draw line for humans as well or not
    """
    e = Embed()
    e.setTitle("Thanks for Participating!")
    s = ctx.db.sql.session()

    most_bites = {}
    most_cures = {}

    # NOTE: Deduplicating these queries could be nice
    query = s.query(HalloweenLog.race, HalloweenLog.user_id, sa.func.count(HalloweenLog.user_id)).filter(
        HalloweenLog.server_id == ctx.guild_id,
        HalloweenLog.user_id != HalloweenLog.target_id,
        HalloweenLog.previous != HalloweenLog.race,
        HalloweenLog.race.in_(list(IMMUNE_TABLE.keys())),
    )
    monsters = (
        query.group_by(HalloweenLog.user_id, HalloweenLog.race)
        .order_by(sa.func.count(HalloweenLog.user_id).desc(), HalloweenLog.race)
        .all()
    )

    query = s.query(HalloweenLog.previous, HalloweenLog.user_id, sa.func.count(HalloweenLog.user_id)).filter(
        HalloweenLog.server_id == ctx.guild_id,
        HalloweenLog.user_id != HalloweenLog.target_id,
        HalloweenLog.previous != HalloweenLog.race,
        HalloweenLog.previous.in_(list(IMMUNE_TABLE.keys())),
    )
    hunters = (
        query.group_by(HalloweenLog.user_id, HalloweenLog.previous)
        .order_by(sa.func.count(HalloweenLog.user_id).desc())
        .all()
    )

    # FIXME: This can be simplified if we can get only top per race from db
    for monster, user, count in monsters:
        if monster not in most_bites or count > most_bites[monster][1]:
            most_bites[monster] = (user, count)

    for hunted, user, count in hunters:
        hunter = IMMUNE_TABLE.get(hunted)
        if hunter not in most_cures or count > most_cures[hunter][1]:
            most_cures[hunter] = (user, count)

    most_turned = (
        s.query(HalloweenLog.target_id, sa.func.count(HalloweenLog.target_id))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
        )
        .group_by(HalloweenLog.target_id)
        .order_by(sa.func.count(HalloweenLog.target_id).desc())
        .first()
    )
    # TODO: Get total bites/cures and players
    bites = (
        s.query(sa.func.count(HalloweenLog.race))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
            HalloweenLog.race.in_(IMMUNE_TABLE),
        )
        .first()
    )
    cures = (
        s.query(sa.func.count(HalloweenLog.race))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.user_id != HalloweenLog.target_id,
            HalloweenLog.previous != HalloweenLog.race,
            HalloweenLog.race == Race.Human,
        )
        .first()
    )
    players = (
        s.query(sa.func.count(sa.distinct(HalloweenLog.user_id))).filter(HalloweenLog.server_id == ctx.guild_id).first()
    )
    unique_victims = s.query(sa.func.count(Halloween.server_id)).filter(Halloween.server_id == ctx.guild_id).first()
    turns = (
        s.query(sa.func.count(HalloweenLog.server_id))
        .filter(
            HalloweenLog.server_id == ctx.guild_id,
            HalloweenLog.previous != HalloweenLog.race,
        )
        .first()
    )
    PLURALS = {
        Race.Human: "Humans",
        Race.Vampire: "Vampires",
        Race.Werewolf: "Werewolves",
        Race.Zombie: "Zombies",
        Race.Hunter: "Hunters",
        Race.Huntsmen: "Huntsmen",
        Race.Enchanter: "Enchanters",
    }

    stats = [
        ("Players", players[0]),
        ("Victims", unique_victims[0]),
        ("Turns", turns[0]),
        ("Bites", bites[0]),
        ("Cures", cures[0]),
    ]
    e.addField("Total", "\n".join([f"`{i[0]:10}`: `{i[1]:>5}`" for i in stats]))
    total = sorted(Halloween.get_total(s, ctx.guild_id).items(), key=lambda x: x[1], reverse=True)
    total = "\n".join([f"`{PLURALS.get(i[0], i[0]):10}`: `{i[1]:>5}`" for i in total])
    e.addField("Member Counts", total)
    # TODO: format nicely!
    def get_username(user_id):
        from MFramework import Guild_Member

        return ctx.cache.members.get(int(user_id), Guild_Member(user=User(username=user_id))).user.username

    if most_bites:
        most_bites = "\n".join([f"**{k.name}**: `{get_username(v[0])}` ({v[1]})" for k, v in most_bites.items()])
        e.addField("Most Bites as", most_bites, True)
    if most_cures:
        most_cures = "\n".join([f"**{k.name}**: `{get_username(v[0])}` ({v[1]})" for k, v in most_cures.items()])
        e.addField("Most Cures as", most_cures, True)
    turned_user = get_username(most_turned[0])
    e.addField("Mostly Turned", f"`{turned_user}` ({most_turned[1]})")

    history = (
        s.query(HalloweenLog)
        .filter(HalloweenLog.server_id == ctx.guild_id, HalloweenLog.previous != HalloweenLog.race)
        .order_by(HalloweenLog.timestamp.asc())
        .all()
    )

    from collections import Counter

    counters = Counter(list(Race))
    races = {i: {"y": [], "t": []} for i in list(Race)}
    counters[Race.Human] = stats[1][1]

    import pandas as pd

    for entry in history:
        counters[entry.race] += 1
        counters[entry.previous] -= 1
        if not include_humans and entry.race is Race.Human:
            continue
        t = pd.to_datetime(entry.timestamp).tz_convert(None)
        races[entry.race]["y"].append(counters[entry.race])
        races[entry.race]["t"].append(t)

    import matplotlib.pyplot as plt

    fig, ax = plt.subplots(figsize=(12, 8))
    ax.patch.set_facecolor("#333333")
    plt.style.use(["dark_background"])

    LINE_STYLES = {
        Race.Human: "dashed",
        Race.Vampire: "dotted",
        Race.Werewolf: "dotted",
        Race.Zombie: "dotted",
        Race.Hunter: "-",
        Race.Huntsmen: "-",
        Race.Enchanter: "-",
    }

    COLORS = {
        Race.Human: "white",
        Race.Vampire: "#942710",
        Race.Werewolf: "#aa6b00",
        Race.Zombie: "#03803c",
        Race.Hunter: "#ad4949",
        Race.Huntsmen: "#be923d",
        Race.Enchanter: "#4abe5f",
    }

    for race, v in races.items():
        if not include_humans and race is Race.Human:
            continue
        y, t = v.values()
        df = pd.Series(y, index=t)
        # df = df.resample(resample).count()
        ax.plot(df, label=race.name, marker="", linestyle=LINE_STYLES.get(race), color=COLORS.get(race))

    from mlib import graphing

    graphing.set_locator(ax, "Day", 3)
    fig.autofmt_xdate()
    now = datetime.now()

    graphing.set_legend(
        ax,
        f"Halloween {now.year} Summary",
        "Population",
        "Days",
        f"upper {'left' if not include_humans else 'right'}",
        framealpha=0,
    )
    fig.tight_layout()

    img = graphing.create_image(fig)
    filename = f"halloween_summary-{datetime.now().year}.png"
    e.setImage("attachment://" + filename).setColor(16744206)
    await ctx.send(embeds=[e], attachments=[Attachment(file=img, filename=filename)])

@register(group=Groups.SYSTEM, interaction=False)
async def crossref(ctx: Context, *, language):
    """
    Description to use with help command
    """
    s = ctx.db.sql.session()
    players = s.query(sa.distinct(HalloweenLog.user_id)).filter(HalloweenLog.server_id == ctx.guild_id).all()
    unique_victims = s.query(Halloween.user_id).filter(Halloween.server_id == ctx.guild_id).all()
    unique_victims = [i for i in unique_victims if i not in players]
    not_found = []
    for victim in unique_victims:
        m = ctx.cache.members.get(victim[0])
        if not m:
            not_found.append(victim[0])
    from ...database import log, types

    users = {}
    avg_chat = []
    avg_voice = []
    with_infractions = []
    for left in not_found:
        r = log.Statistic.filter(s, server_id=ctx.guild_id, user_id=left).all()
        infractions = s.query(sa.func.count(log.Infraction.user_id)).filter(log.Infraction.user_id == left).first()
        if infractions[0]:
            with_infractions.append((left, infractions[0]))
            continue

        chatted = False
        voiced = False
        for _ in r:
            if _.name == types.Statistic.Chat:
                avg_chat.append((left, _.value))
                chatted = True
            elif _.name == types.Statistic.Voice:
                avg_voice.append((left, _.value))
                voiced = True
        if not chatted:
            avg_chat.append((left, 0))
        if not voiced:
            avg_voice.append((left, 0))

    top_chat = sorted(avg_chat, key=lambda x: x[1])
    top_voice = sorted(avg_voice, key=lambda x: x[1])
    tc = max(top_chat[0], top_chat[-1], key=lambda x: x[1])
    tv = max(top_voice[0], top_voice[-1], key=lambda x: x[1])
    avg_chat = [i[1] for i in avg_chat if i[1] < 1000]
    avg_voice = [i[1] for i in avg_voice if i[1] < 1000]
    with_infractions = [i[1] for i in with_infractions if i[1]]
    return f"{len(avg_chat)}/{len(ctx.cache.members)}\nAvg chat activity: {sum(avg_chat)/len(avg_chat)}\nAvg voice activity: {sum(avg_voice)/len(avg_voice)}\nWith infractions: {len(with_infractions)}\nAvg infractions: {sum(with_infractions)/len(with_infractions)}\nChatter: <@{tc[0]}> - {tc[1]}\nVoice: <@{tv[0]}> - {tv[1]}"


def checkMagazine(magazine, note):
    answer = 'No'
    magazine_dic = {}
    for word in magazine:
        if word not in magazine_dic:
            magazine_dic[word] = 1
        else:
            magazine_dic[word] = magazine_dic[word] + 1

    for word in note:
        if word not in magazine_dic:
            answer = 'No'
        else:
            magazine_dic[word] = magazine_dic[word] - 1

            if magazine_dic[word] < 0:
                answer = 'No'
                break
            else:
                answer = 'Yes'
    return answer

"""
give me one grand today night
give one grand today
"""

# magazine = ['ive', 'got', 'a', 'lovely', 'bunch', 'of', 'coconuts']
# note = ['ive', 'got', 'some', 'coconuts']
magazine = ['give', 'me', 'one', 'grand', 'today', 'night']
note = ['give', 'one', 'grand', 'today']

# magazine = ['two', 'times', 'three', 'is', 'not', 'four']
# note = ['two', 'times', 'two', 'is', 'four']

print(checkMagazine(magazine, note))


# -----------------------------------------------------------
# The db class contains all functionality towards the mongoDB.
# It is a singleton and the instance can be obtained from
# db.get_instance() method.
#
#
# 2021 Andre Scheir Johansson
# email: scheir5@hotmail.se
# -----------------------------------------------------------

from singleton import Singleton
import json
from customer import Customer
import pymongo
from bson import ObjectId
from collections import ChainMap
import os

# Default values for mongoDB if environment variables are not set.
DB_SERVER_DEFAULT = "mongodb://localhost:27017/"
DB_NAME_DEFAULT = "test2"

@Singleton
class db:
    """
    Singleton which interacts with mongoDB.

    Attributes
    ----------
    client: mongDB client
    db: mongoDB database taken from client

    """
    def __init__(self) -> None:
        """
        Constructor
        """
        # Open connection once
        # Get server and db name from environment variables
        # MONGODB_SERVER and DB_NAME, set in activation of
        # python environemt. Else, use default value
        db_server = os.getenv("MONGODB_SERVER")
        db_server = db_server if db_server else DB_SERVER_DEFAULT
        db_name = os.getenv("DB_NAME")
        db_name = db_name if db_name else DB_NAME_DEFAULT
        self.client = pymongo.MongoClient(db_server)
        self.db = self.client[db_name]

    def __str__(self):
       return f'DB instance {self.client}, {self.db}'

    def create_customer(self, new_cust) -> dict:
        """
        Store a new customer to the db if customer
        doesnt allready exist.

        :param new_cust Customer object
        """
        column = self.db["customers"]
        # Need to handle a case when customer allready exists
        # If we find a query skip inserting and tell user
        query_res = column.find_one({"name":new_cust.name})
        if query_res:
            return False

        #Insert customer, query it and return to user
        column.insert_one(new_cust.dictify())
        return self.get_customer(new_cust.name)

    def get_customer(self, customer) -> dict:
        """
        Query a customer from the database

        :param customer Customer object to query
        """
        column = self.db["customers"]
        query_res = column.find_one({"name":customer},{"_id":0})
        return query_res

    def create_order(self, order) -> dict:
        """
        Store/create a new order to the database

        :param order Order to create
        """
        column = self.db["orders"]
        created_order_id = column.insert_one(order.dictify()).inserted_id

        # Order is created, get the db entry and return it
        return self.get_order(created_order_id)

    def get_order(self, order_id) -> dict:
        """
        Query an order from the database

        :param order_id id of order to query
        """

        column = self.db["orders"]
        # Check if Order id has wrong format
        try:
            query_res = column.find_one({"_id":ObjectId(order_id)})
        except:
            return None

        # Order id format ok, but order doesnt exist
        if not query_res:
            return None

        # Nasty bug where the id field in the mongoDB is nasty.
        # Work around by deleting the field and inserting it again as a string.
        query_res["id"] = str(query_res.get("_id"))
        del[query_res["_id"]]
        return query_res

    def update_order(self, order_id, order):
        """
        Update an order in the databse

        :param order_id id of order to update
        :param order Order object to replace current order
        """
        column = self.db["orders"]
        column.update_many({"_id":ObjectId(order_id)},{"$set":order.dictify()})
        return self.get_order(order_id)

    def get_item(self, item) -> dict:
        """
        Retrieve item from warehouse db.

        :param item Item to rerieve
        """

        column = self.db["warehouse"]
        query_res = column.find_one({"item":item},{"_id":0})
        return query_res

    def get_discount(self, cust_type, item) -> int:
        """
        Retrieve discount for customer type for item.

        :param cust_type Customer type.
        :param item Item to check for discount.
        """
        column = self.db["discounts"]
        # Get the discount document
        query_res = column.find_one({cust_type : { "$exists": True }})
        # Get the item from the document
        return query_res.get(cust_type).get(item, 0)

    def get_promotions(self):
        """
        Retrieve all promotion documents from DB
        """
        column = self.db["promotions"]
        query_res = column.find({},{"_id":0})
        return list(query_res)


import csv
import json
import requests

def get_json(url):
    try:
        print("HTTP GET", url)
        r = requests.get(url)
        json_dict = r.json()
        return json_dict
    except requests.exceptions.RequestException as error:
        print(error)

def download_json(url, filepath):
    try:
        print("HTTP GET", url)
        r = requests.get(url)
        json_dict = r.json()
        json_str = json.dumps(json_dict, indent=2, ensure_ascii=False)
        with open(filepath, "w") as f:
            f.write(json_str)
    except requests.exceptions.RequestException as error:
        print(error)

def download_csv(url, filepath):
    try:
        print("HTTP GET", url)
        r = requests.get(url)
        with open(filepath, 'w') as f:
            writer = csv.writer(f)
            for line in r.iter_lines():
                writer.writerow(line.decode('utf-8').split(','))
    except requests.exceptions.RequestException as error:
        print(error)


import portage

from portage.dbapi import porttree
from gentoolkit.eclean.search import (port_settings)

def get_package_uris(package):
    cpv = package.cpv
    metadata = {
        "EAPI"    : port_settings["EAPI"],
        "SRC_URI" : package.environment("SRC_URI", False),
    }
    use = frozenset(port_settings["PORTAGE_USE"].split())

    alist = porttree._parse_uri_map(cpv, metadata, use=use)
    aalist = porttree._parse_uri_map(cpv, metadata)

    if "mirror" in portage.settings.features:
        uris = aalist
    else:
        uris = alist

    return uris


import requests
from bs4 import BeautifulSoup

def baixar_arquivo(url, endereco):
    resposta = requests.get(url)
    with open(endereco, 'wb') as novo_arquivo:
        novo_arquivo.write(resposta.content)

if __name__ == '__main__':
    url = 'https://math.mit.edu/classes/18.745/Notes/Lecture_1_Notes.pdf'
    baixar_arquivo(url, 'test.pdf')


import numpy as np
import sys

#
# simple test python
#

def main():
    assert len(sys.argv) > 1
    N = int(sys.argv[1])
    assert N < 1e7
    uni = np.random.uniform(-10,10,N)
    print(np.mean(uni), np.std(uni))
    nrm = np.random.normal(0, np.std(uni), N)
    print(np.mean(nrm), np.std(nrm), N)

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-

import pytz
import datetime
import pkg_resources

from django.template import Context, Template
from django.utils.encoding import smart_text

from xblock.core import XBlock
from xblock.fields import Scope, Integer, String, Boolean
from xblock.fragment import Fragment

from xmodule.util.duedate import get_extended_due_date

class TxtXBlock(XBlock):
    display_name = String(
        display_name=u"Название",
        help=u"Название задания, которое увидят студенты.",
        default=u'txt',
        scope=Scope.settings
    )

    question = String(
        display_name=u"Вопрос",
        help=u"Текст задания.",
        default=u"Вы готовы?",
        scope=Scope.settings
    )

    correct_answer = String(
        display_name=u"Правильный ответ",
        help=u"Правильный ответ",
        default="",
        scope=Scope.settings
    )

    weight = Integer(
        display_name=u"Максимальное количество баллов",
        help=(u"Максимальное количество баллов",
              u"которое может получить студент."),
        default=100,
        scope=Scope.settings
    )

    max_attempts = Integer(
        display_name=u"Максимальное количество попыток",
        help=u"",
        default=0,
        scope=Scope.settings
    )

    attempts = Integer(
        display_name=u"Количество сделанных попыток",
        default=0,
        scope=Scope.user_state
    )

    points = Integer(
        display_name=u"Количество баллов студента",
        default=None,
        scope=Scope.user_state
    )

    answer = String(
        display_name=u"Ответ пользователя",
        default="",
        scope=Scope.user_state
    )

    keywords = String(
        display_name=u"Слова, вхождение которых обязательно.",
        help=u"Обязательные слова",
        default="",
        scope=Scope.settings
    )

    grading_threshold = Integer(
        display_name=u"Количество баллов студента",
        default=None,
        scope=Scope.settings
    )

    has_score = True

    @staticmethod
    def resource_string(path):
        """
        Handy helper for getting resources from our kit.
        """
        data = pkg_resources.resource_string(__name__, path)
        return data.decode("utf8")

    @staticmethod
    def load_resources(js_urls, css_urls, fragment):
        """
        Загрузка локальных статических ресурсов.
        """
        for js_url in js_urls:

            if js_url.startswith('public/'):
                fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url))
            elif js_url.startswith('static/'):
                fragment.add_javascript(_resource(js_url))
            else:
                pass

        for css_url in css_urls:

            if css_url.startswith('public/'):
                fragment.add_css_url(self.runtime.local_resource_url(self, css_url))
            elif css_url.startswith('static/'):
                fragment.add_css(_resource(css_url))
            else:
                pass

    def past_due(self):
            """
            Проверка, истекла ли дата для выполнения задания.
            """
            due = get_extended_due_date(self)
            if due is not None:
                if _now() > due:
                    return False
            return True

    def is_course_staff(self):
        """
        Проверка, является ли пользователь автором курса.
        """
        return getattr(self.xmodule_runtime, 'user_is_staff', False)

    def is_instructor(self):
        """
        Проверка, является ли пользователь инструктором.
        """
        return self.xmodule_runtime.get_user_role() == 'instructor'

    # views

    def studio_view(self, *args, **kwargs):
        """
        Отображение txtXBlock разработчику (CMS).
        """
        context = {
            "display_name": self.display_name,
            "weight": self.weight,
            "question": self.question,
            "correct_answer": self.correct_answer,
            "answer": self.answer,
            "max_attempts": self.max_attempts,
        }

        fragment = Fragment()
        fragment.add_content(
            render_template(
                'static/html/txt_edit.html',
                context
            )
        )

        js_urls = (
            "static/js/txt_edit.js",
        )

        css_urls = (
            'static/css/txt.css',
        )

        self.load_resources(js_urls, css_urls, fragment)
        fragment.initialize_js('TxtXBlockEdit')

        return fragment

    def student_view(self, *args, **kwargs):
        """
        Отображение txtXBlock студенту (LMS).
        """

        context = {
            "display_name": self.display_name,
            "weight": self.weight,
            "question": self.question,
            "correct_answer": self.correct_answer,
            "answer": self.answer,
            "attempts": self.attempts,
        }

        if self.max_attempts != 0:
            context["max_attempts"] = self.max_attempts

        if self.past_due():
            context["past_due"] = True

        if self.answer != '{}':
            context["points"] = self.points

        if answer_opportunity(self):
            context["answer_opportunity"] = True

        if self.is_course_staff() is True or self.is_instructor() is True:
            context['is_course_staff'] = True

        fragment = Fragment()
        fragment.add_content(
            render_template(
                'static/html/txt.html',
                context
            )
        )

        js_urls = (
            'static/js/txt.js',
        )

        css_urls = (
            'static/css/txt.css',
        )

        self.load_resources(js_urls, css_urls, fragment)

        fragment.initialize_js('TxtXBlock')
        return fragment

    # handlers

    @XBlock.json_handler
    def studio_submit(self, data, suffix=''):
        self.display_name = data.get('display_name')
        self.question = data.get('question')
        self.weight = data.get('weight')
        self.correct_answer = data.get('correct_answer')
        self.max_attempts = data.get('max_attempts')
        self.keywords = data.get('keywords')
        self.grading_threshold = data.get('grading_threshold')

        return {'result': 'success'}

    @XBlock.json_handler
    def student_submit(self, data, suffix=''):
        pass

def answer_opportunity(self):
    """
    Возможность ответа (если количество сделанное попыток меньше заданного).
    """
    if self.max_attempts <= self.attempts and self.max_attempts != 0:
        return False
    else:
        return True

def _now():
    """
    Получение текущих даты и времени.
    """
    return datetime.datetime.utcnow().replace(tzinfo=pytz.utc)

def _resource(path):  # pragma: NO COVER
    """
    Handy helper for getting resources from our kit.
    """
    data = pkg_resources.resource_string(__name__, path)
    return data.decode("utf8")

def render_template(template_path, context=None):
    """
    Evaluate a template by resource path, applying the provided context.
    """
    if context is None:
        context = {}

    template_str = load_resource(template_path)
    template = Template(template_str)
    return template.render(Context(context))

def load_resource(resource_path):
    """
    Gets the content of a resource
    """
    try:
        resource_content = pkg_resources.resource_string(__name__, resource_path)
        return smart_text(resource_content)
    except EnvironmentError:
        pass


from snakegame.state import Coordinate, GameState, TileState, NORTH, EAST, SOUTH, WEST
from snakegame.board_builder import board_builder
from snakegame.time_manager import TimeManager
import pygame
TEAL = pygame.color.Color(163, 240, 238)
RED = pygame.color.Color(255, 0, 0)
GREEN = pygame.color.Color(0, 255, 0)
TAILGREEN = pygame.color.Color(187, 255, 0)
HEADRED = pygame.color.Color(255, 255, 0)
WALL = pygame.color.Color(0, 171, 165)
WIDTH, HEIGHT = 800, 800

def px_per_tile(surface, state):
    px_x_per_tile = surface.get_width() / state.column_count
    px_y_per_tile = surface.get_height() / state.row_count
    return px_x_per_tile, px_y_per_tile

def tile2px(surface, state, coordinate):
    px_x_per_tile, px_y_per_tile = px_per_tile(surface, state)
    px_x = coordinate.x * px_x_per_tile
    px_y = coordinate.y * px_y_per_tile
    return px_x, px_y, px_x_per_tile, px_y_per_tile

PAINTMAP = {
    TileState.EMPTY: TEAL,
    TileState.SNAKE_HEAD: HEADRED,
    TileState.APPLE: RED,
    TileState.SNAKE_BODY: GREEN,
    TileState.SNAKE_TAIL: TAILGREEN,
    TileState.WALL: WALL
    }

def render_state(surface, state):
    surface.fill(TEAL)
    for row_number in range(0, state.row_count):
        for column_number in range(0, state.column_count):
            c = Coordinate(column_number, row_number)
            pygame.draw.rect(surface, PAINTMAP[state.get_tile(c)], tile2px(surface, state, c))
    pygame.display.flip()

def turn():
    pass

def play():
    window = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Snake_game")
    new_direction = NORTH
    pygame.init()
    s = board_builder(32,32)
    t = TimeManager(s)
    x = 0
    run = True
    last_tick = pygame.time.get_ticks()
    while run:
        this_tick = pygame.time.get_ticks()
        elapsed = this_tick - last_tick
        last_tick = this_tick
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
        pressed_keys = pygame.key.get_pressed()
        if pressed_keys[pygame.K_w]:
            new_direction = NORTH
        elif pressed_keys[pygame.K_d]:
            new_direction = EAST
        elif pressed_keys[pygame.K_s]:
            new_direction = SOUTH
        elif pressed_keys[pygame.K_a]:
            new_direction = WEST
        else:
            pass
        if not s.died:
            s.set_direction(new_direction)
            t.handle_time_passed(elapsed)
        render_state(window, s)
    pygame.quit()

if __name__ == "__main__":
    play()


import os
from PIL import Image
from flask import Flask, flash, request, jsonify, render_template, Response
from flask_cors import CORS
import requests
from werkzeug.utils import secure_filename
from dotenv import load_dotenv
import numpy as np
from numpy import asarray
import tensorflow as tf
import cv2
from skimage.transform import resize
import datetime

global capture, switch, name_class, confidence_score
capture = 0
switch = 0
name_class = ""
confidence_score = ""

load_dotenv()

MODEL = tf.keras.models.load_model("model/3")
CLASS_NAMES = ["Hạt khiếm khuyết- Defect", "Hạt dài - Longberry", "Hạt tròn - Peaberry", "Hạt thượng hạng - Premium"]

UPLOAD_FOLDER = "uploads"
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}

app = Flask(__name__, template_folder='./templates')
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# camera = cv2.VideoCapture(0)
global start_camera
start_camera = 0

CORS(app)   # Cross-origin resource sharing

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def resize_image(image_array):
    # Resize the array to (256, 256)
    # resized_image = resize(image_array, (256, 256, image_array.shape[2]), mode='reflect', anti_aliasing=True)
    # resized_image = image_array.resize((256, 256), Image.ANTIALIAS)
    # return resized_image
    img = Image.fromarray(image_array)
    resized_image = img.resize((256, 256), Image.ANTIALIAS)
    return np.asarray(resized_image)

def upload_file():
    # check if the post request has the file part
    if 'file' not in request.files:
        flash('No file part')
        return "Not a file"
    file = request.files['file']

    # If the user does not select a file, the browser submits an
    # empty file without a filename.
    if file.filename == '':
        flash('No selected file')
        return "empty file"
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    else:
        return "file invalid"
    return filename

def convert_from_bytes_to_numpyarray(filename):
    img = Image.open(UPLOAD_FOLDER+"\\"+filename)
    # asarray() class is used to convert
    # PIL images into NumPy arrays
    numpydata = asarray(img)
    return numpydata

@app.route("/predict",methods=["POST"])
def Predict(img=""):
    switch = 0
    if img != "":
        file_name = img
        switch = 1
    else:
        file_name = upload_file()
    # convert from [256,256,3] -> [[256,256,3]]
    img = convert_from_bytes_to_numpyarray(file_name)
    img = resize_image(img)
    img_batch = np.expand_dims(img, 0)
    predictions = MODEL.predict(img_batch)
    predicted_class = CLASS_NAMES[np.argmax(predictions[0])]
    confidence = np.max(predictions[0])
    data = [{
        'class': predicted_class,
        'confidence': round(float(confidence),4)
    }]
    print(data)
    if switch :
        return data
    resp = jsonify(data=data)
    return resp

@app.route('/video_feed')
def video_feed():
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

def gen_frames():  # generate frame by frame from camera
    global capture
    while True:
        if start_camera == 0:
            break
        success, frame = camera.read()
        if success:
            if(capture):
                capture=0
                now = datetime.datetime.now()
                p = os.path.sep.join(['uploads', "shot_{}.jpg".format(str(now).replace(":",''))])
                cv2.imwrite(p, frame)
                file_name = p[8:]
                with app.app_context(), app.test_request_context():
                    data = Predict(file_name)
                    global name_class, confidence_score
                    name_class = data[0]['class']
                    confidence_score = data[0]['confidence']
                    break

            try:
                ret, buffer = cv2.imencode('.jpg', cv2.flip(frame,1))
                frame = buffer.tobytes()
                yield (b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
            except Exception as e:
                pass
        else:
            break

@app.route('/requests',methods=['POST','GET'])
def tasks():
    global switch,camera
    if request.method == 'POST':
        if request.form.get('click') == 'Capture':
            global capture
            capture=1
        elif  request.form.get('stop') == 'Stop/Start':
            if(switch==1):
                switch=0
                global name_class, confidence_score
                name_class = ""
                confidence_score = ""
                camera.release()
                cv2.destroyAllWindows()
            else:
                global start_camera
                start_camera = 1
                camera = cv2.VideoCapture(0)
                switch=1
    elif request.method=='GET':
        return render_template('home.html',name_class=name_class,confidence_score=confidence_score)
    return render_template('home.html',name_class=name_class,confidence_score=confidence_score)

@app.route("/home",methods=["GET"])
def Home():
    return render_template('home.html',name=name_class,confidence_score=confidence_score)

if __name__ == "__main__":
    #Run the app in the application context
    with app.app_context():
        app.run(debug=True)

# camera.release()
# cv2.destroyAllWindows()


from repository.banco_de_dados import BancoDeDados

from utils.relatorio import obter_dados_acao

banco = BancoDeDados()

def menu_clientes():
    working = True

    while working:
        print(
            """
           Consultar informações de usuário:

            1 - Inserir novo cliente
            2 - Alterar informações de cliente
            3 - Mostrar informações de cliente
            4 - Desativar cliente (deleção virtual)
            0 - Retornar ao menu anterior
        """
        )

        choosen_option = input("Escolha uma opção: ")

        if choosen_option == "1":
            return banco.inserir_cliente()

        elif choosen_option == "2":
            return banco.alterar_cliente()

        elif choosen_option == "3":
            return banco.selecionar_cliente()

        elif choosen_option == "4":
            return banco.desativar_cliente()

        elif choosen_option == "0" or choosen_option == "":
            return

        else:
            print("Opção inválida. Tente novamente.")

# %%
def menu_acoes():
    working = True

    while working:
        print(
            """
           Consultar informações de ações:

            1 - Cadastrar Ordem de Compra
            2 - Deletar ação
            0 - Retornar ao menu anterior
        """
        )

        choosen_option = input("Escolha uma opção: ")

        if choosen_option == "1":
            return banco.inserir_acao()

        elif choosen_option == "2":
            return banco.deletar_acao()

        elif choosen_option == "0" or choosen_option == "":
            return

        else:
            print("Opção inválida. Tente novamente.")

# %%
def menu_carteira():
    working = True

    while working:
        print(
            """
           Consultar performace de carteiras:

            1 - Performace
            0 - Retornar ao menu anterior
        """
        )

        choosen_option = input("Escolha uma opção: ")

        if choosen_option == "1":
            return banco.analise_carteira()

        elif choosen_option == "0" or choosen_option == "":
            return

        else:
            print("Opção inválida. Tente novamente.")

# %%
def menu_relatorio():
    working = True

    while working:
        print(
            """
           Consultar informações de carteiras:

            1 - Obter relatório
            2 - consultar acao
            0 - Retornar ao menu anterior
        """
        )

        choosen_option = input("Escolha uma opção: ")

        if choosen_option == "1":
            return banco.relatorio_carteira()

        elif choosen_option == "2":
            return obter_dados_acao()

        elif choosen_option == "0" or choosen_option == "":
            return

        else:
            print("Opção inválida. Tente novamente.")


#https://programmers.co.kr/learn/courses/30/lessons/85002
#프로그래머스-복서 정렬하기(위클리 챌린지)
def solution(weights, head2head):
    answer, info = [],[]
    n = len(weights)
    for i in range(n):
        big,fight,win = 0,0,0
        for j in range(n):
            if head2head[i][j]!='N':
                if head2head[i][j]=='W':
                    if weights[i]<weights[j]: big+=1
                    win+=1
                fight +=1
        # #승률, 자신보다 몸무게가 무거운 복서를 이긴 횟수, 자기 몸무게,번호
        info.append((win/fight*100,big,weights[i],i) if win>0 else (0,big,weights[i],i))

    info = sorted(info,key = lambda x: (x[0],x[1],x[2],-x[3]),reverse = True)
    return [info[i][-1]+1 for i in range(n)]


#!/usr/bin/env python3
###########################################################################
#
# xasyFile implements the loading, parsing, and saving of an xasy file.
#
#
# Author: Orest Shardt
# Created: June 29, 2007
#
############################################################################

from string import *
import xasy2asy as x2a
import io
import re

class xasyParseError(Exception):
    """A parsing error"""
    pass

class xasyFileError(Exception):
    """An i/o error or other error not related to parsing"""
    pass

def extractTransform(line):
    """Returns key and the new transform."""
    # see https://regex101.com/r/6DqkRJ/4 for info
    mapString = x2a.xasyItem.mapString
    testMatch = re.match(
        r'^{0:s}\s*\(\s*\"([^\"]+)\"\s*,\s*\(([-\d, .]+)\)\s*\)'.format(mapString), line.strip())
    if testMatch is None:
        mapOnlyMatch = re.match(r'^{0:s}\s*\(\s *\"([^\"]+)\"\s*\)'.format(mapString), line.strip())
        if mapOnlyMatch is None:
            return None
        else:
            key = mapOnlyMatch.group(1)
            return key, x2a.identity()
    else:
        key = testMatch.group(1)
        rawStr = testMatch.group(2)
        rawStrArray = rawStr.split(',')

        if len(rawStrArray) != 6:
            return None
        transf = [float(val.strip()) for val in rawStrArray]
        return key, x2a.asyTransform(transf)

def extractTransformsFromFile(fileStr):
    transfDict = {}
    maxItemCount = 0
    with io.StringIO() as rawCode:
        for line in fileStr.splitlines():
            test_transf = extractTransform(line.rstrip())
            if test_transf is None:
                rawCode.write(line + '\n')
            else:
                key, transf = test_transf
                if key not in transfDict.keys():
                    transfDict[key] = []
                transfDict[key].append(transf)

                # see https://regex101.com/r/RgeBVc/2 for regex

                testNum = re.match(r'^x(\d+)($|:.*$)', key)
                if testNum is not None:
                    maxItemCount = max(maxItemCount, int(testNum.group(1)))
        final_str = rawCode.getvalue()
    return final_str, transfDict, maxItemCount

def saveFile(file, xasyItems, asy2psmap):
    """Write a list of xasyItems to a file"""
    for item in xasyItems:
        file.write(item.getTransformCode(asy2psmap))

    for item in xasyItems:
        file.write(item.getObjectCode(asy2psmap))

    file.write('size('+str(asy2psmap*x2a.yflip())+'); '+ x2a.xasyItem.resizeComment+'\n')


import re
from click import style
from uno.model.card import Card

class TerminalUnoView:
    """A view of the Uno card game for the Terminal.
    """

    def __init__(self):
        self.last_message_seen = 0

    def play(self, game):
        "Plays a game"
        self.greet()
        while not game.is_over():
            action = self.get_action(game)
            game.play_action(action)
            self.show_unseen_messages(game)
        self.conclude(game)

    def greet(self):
        print("Welcome to Uno!")

    def get_action(self, game):
        "gets an action from a player."
        player = game.current_player()
        print('-' * 80)
        print(f"It's {player.name}'s turn.")
        state = game.get_state()
        actions = game.get_actions()
        if player.is_automated:
            action = player.choose_action(state, actions)
        else:
            print("Opponent hands:", state["opponent_hands"])
            print("Your hand: ", ', '.join(self.color_card(card) for card in state["hand"]))
            print("Top card: ", self.color_card(state["top_card"]))
            print("Choose an action:")
            for i, action in enumerate(actions):
                print(f"{i}. {self.choice_text(action)}")
            choice = self.get_integer_input(len(actions))
            action = actions[choice]
        return action

    def choice_text(self, action):
        if action["action"] == "pass":
            return "Pass."
        elif action["action"] == "draw":
            return f"Draw a card."
        elif action["action"] == "play":
            card_text = self.color_card(action['card'])
            if "color" in action:
                return f"Play {card_text} and set the color to {action['color']}."
            else:
                return f"Play {card_text}."

    def show_unseen_messages(self, game):
        """Prints out all messages which have not yet been seen, and then
        updates self.last_message_seen to record that these messages have
        been seen.
        """
        unseen_messages = game.messages[self.last_message_seen:]
        for message in unseen_messages:
            print(' - ' + self.color_message(message))
        self.last_message_seen = len(game.messages)

    def get_integer_input(self, maximum):
        "Gets an integer from the user less than maximum."
        while True:
            response = input("> ")
            if response.isdigit():
                num = int(response)
                if 0 <= num and num < maximum:
                    return num
            print("Invalid input.")

    def conclude(self, game):
        print(f"Congratulations to {game.winner().name}")

    def color_card(self, card):
        "Formats a card's code using color for the Terminal"
        if card.color == "RED":
            return style(card.code, fg="red")
        elif card.color == "YELLOW":
            return style(card.code, fg="yellow")
        elif card.color == "GREEN":
            return style(card.code, fg="green")
        elif card.color == "BLUE":
            return style(card.code, fg="blue")
        elif card.color == "WILD":
            return style(card.code, fg="bright_black")

    def color_message(self, message):
        """Searches for card codes in a message. If found, replaces them with colored text.
        This method uses regular expressions, an extremely powerful form of pattern-matching
        which you haven't learned yet.
        """
        pattern = "[RYGBW][0123456789SRDWX]"
        matches = re.findall(pattern, message)
        for match in matches:
            message = message.replace(match, self.color_card(Card(match)))
        return message


"""
To run the app, do the following.

$ export FLASK_APP=app.py
$ flask run
 * Running on http://127.0.0.1:5000/

"""
import sys

from flask import Flask, jsonify, render_template, request

import logging

sys.path.append('.')
from helpers.score import calculate_score
from helpers.io import load_data, write_response, load_responses

app = Flask(__name__)
logger = logging.getLogger('__main__')
logging.basicConfig(filename='error.log', level=logging.DEBUG)
logger.info('Logging configured')

@app.route("/questions", methods=['GET', 'POST'])
def get_data():
    "Endpoint for getting questions and submitting respones"
    rows = load_data()

    if request.method == 'GET':
        return jsonify({'questions': [r[1] for r in rows]}), 200

    if request.method == 'POST':
        return jsonify({'answers': [float(r[2]) for r in rows]}), 200

@app.route("/submit/<id>/<response>", methods=['POST'])
def submit_responses(id, response):
    r = {'id': id, 'response': response}
    _ = write_response(r)
    return 'Submitted {}'.format(r), 200

@app.route("/get_score")
def get_score():
    "Calculate a score from submitted responses"
    data = load_data()
    correct_answers = [r[2] for r in data]
    responses = load_responses()
    score = calculate_score(responses, correct_answers)
    return jsonify({'score': score}), 200


# noinspection PyUnusedLocal
import time
def main(device, *args, **kwargs):
    # Clean the android browser.
    print("Before Experiment: clear the browser")
    device.shell("pm clear com.android.chrome")
    time.sleep(5)
    # Sleep for 60 seconds so device is
    print("Sleep for 60 seconds")
    time.sleep(60)


from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from .models import Post

from apis.note_api import parse_post_request

def notesheet(request):
    posts = Post.objects.all()
    return render(request, 'web-notes/note.html', {'posts': posts})

def note_save(request):

    posts = Post.objects.all()
    posts_keys = {post.post_id for post in Post.objects.all()}
    new_posts = {key: value for key, value in request.POST.items() if (key.startswith('name:') \
                 and value.strip())}
    new_posts_keys = set(new_posts)

    # 1. 새로 생성
    for key in (new_posts_keys - posts_keys):
        color, coords = parse_post_request(key)
        Post.objects.create(post_id=key,
                        color=color,
                        text=new_posts[key],
                        coordinates=coords,
                       )

    # 2. 삭제
    for d in (posts_keys - new_posts_keys):
        Post.objects.get(pk=d).delete()

    # 3. 수정
    for key in posts_keys.intersection(new_posts_keys):
        updated = posts.get(pk=key)
        updated.text = new_posts[key]
        updated.save()

    return HttpResponseRedirect(reverse('web-notes:note-sheet'))


from ast import operator
import imp
import json
from dsutils.de.utils import dbg, get_var_name
import fnmatch
import functools
import os
import shutil
from tqdm import tqdm
import pandas as pd
import sys
import subprocess

#subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'tabulate'])
from tabulate import tabulate

def get_dir_and_doc_paths(path):
    if os.path.isfile(path):
        dir_path = get_parent_dir(path)
        filepath = path
    elif os.path.isdir(path):
        dir_path = path
        filepath = get_child_ext_path(dir_path=dir_path, ext='pdf')
    else:
        print('enter an existing path')
    return dir_path, filepath

def join_parentpath_childnames(parentpath,childnames):
    fullpaths = map(functools.partial(os.path.join,
                        parentpath),
        childnames)
    return list(fullpaths)

# find first data subdir in path
def get_data_path(wd = os.getcwd()):
    dircount = 0
    dirs = list(wd.split(os.path.normpath(os.sep)))+['data']
    for i in range(len(dirs)):
        data_dir = os.path.normpath(os.sep)+os.path.join(*list(dirs[:-i]),'data')
        if os.path.isdir(data_dir):
            return data_dir
        else:
            dircount += 1
    return None

def get_datafile_path(filename, data_path=get_data_path(), format='csv'):
    filepath = os.path.join(data_path,filename)
    if '.xls' in filename and 'csv' in format :
        filepath = xls_to_csv(filepath)
    return filepath

# lists paths with certain extension
def list_ext(path,
            exts,
            invert=False): # return files WITHOUT such extensions instead
    path_list = list()
    for ext in exts:
        if type(ext) == list:
            ext = ext[0]
        for filename in os.listdir(path):
            filepath = os.path.join(path,filename)
            ends_ext = filepath.endswith(ext)
            is_file = os.path.isfile(filepath)
            has_ext = ends_ext and is_file
            if not invert:
                ret_ext = has_ext
                if ret_ext:
                    path_list.append(filepath)
            elif is_file:
                ret_ext = not has_ext
                if ret_ext:
                    path_list.append(filepath)
    return path_list

# get one child with requested extension
def get_child_ext_path(dir_path, ext):
    child = list_ext(path=dir_path,exts=[ext])[0]
    return child

# creates a folder with the document's name and moves the document in it
def mv_to_custom_dir(doc_path):
    split_path = os.path.split(doc_path)
    ext = os.path.splitext(doc_path)[-1]
    new_dir_name = split_path[-1][:-len(ext)] # same as filename, without the extension
    filename = split_path[-1]
    input_dir_path = split_path[-2]

    new_dir_path = os.path.join(input_dir_path,new_dir_name.replace(" ", "_"))
    new_doc_path = os.path.join(new_dir_path,filename.replace(" ", "_"))

    if not os.path.exists(new_dir_path):
        os.mkdir(new_dir_path)
    if not os.path.exists(new_doc_path):
        os.rename(doc_path,new_doc_path)
    else:
        print('Warning: a document named {0} already exists. Deleting the newer copy.'.format(filename))
        os.remove(doc_path)
    return
def get_parent_dir(path):
    return os.path.split(path)[-2]

def get_child_dir_paths(dir_path):
    child_names = os.listdir(dir_path)
    child_paths = [os.path.join(dir_path,child_name) for child_name in child_names]
    dir_paths = [child_path for child_path in child_paths if os.path.isdir(child_path)]
    return dir_paths

# make directory if it doesn't exist
def mkdir_no_over(dir_name):
    if not os.path.exists(dir_name):
        os.makedirs(dir_name)
    return dir_name

def save_file(filepath, data):
    with open(filepath, 'w+') as f:
        f.write(data)

def save_data(doc_dir,
                dir_name,
                file_name,
                content,
                ):
    dirpath = os.path.join(doc_dir,dir_name)
    mkdir_no_over(dir_name)
    filepath = os.path.join(dirpath,file_name)
    save_data(filepath, content)

def get_final_files(treeroot, pattern):
    results = []
    for base, dirs, files in os.walk(treeroot):
        goodfiles = fnmatch.filter(files, pattern)
        results.extend(os.path.join(base, f) for f in goodfiles)
    print('{0} FILES FOUND'.format(len(results)))
    return results

def store_final_files(final_name, data_path, target_folder):
    final_files = get_final_files(data_path, final_name)
    for t in tqdm(final_files, desc='files stored', leave=False):
        path = os.path.normpath(t)
        path_sects = path.split(os.sep)
        target_name = path_sects[-3]+'_'+final_name
        target_path = os.path.join(target_folder,target_name)
        shutil.copyfile(t, target_path)

def try_read(path, ext=None, alt=''):
    try:
        if ext is None:
            filepath = path
        else:
            filepath = get_child_ext_path(path, ext)
        with open(filepath) as f:
            content = f.read()
    except (IndexError, FileNotFoundError):
        content = alt
    return content

def xls_to_csv(xls_path, csv_path=''):
    read_file = pd.read_excel(xls_path)
    if not csv_path:
        csv_path = os.path.join(get_parent_dir(xls_path),os.path.splitext(os.path.basename(xls_path))[0]+'.csv')
    read_file.to_csv(csv_path, index = None, header=True)
    return csv_path

def get_filename(path):
    filename = os.path.basename(path)
    return filename
def get_extension(path):
    ext = os.path.splitext(path)[-1]
    return ext

def sample_csv(path, size=5, filename='', in_col_select_rows={}, operator='OR'):
    if not filename:
        filename = get_filename(path)
    sample_path = filename+'_sample'+get_extension(path)
    df = pd.read_csv(path)
    df_ = None
    if in_col_select_rows:
        if operator == 'OR':
            for c,strs in in_col_select_rows.items():
                dbg(c)
                c =0
                for s in strs:
                    c +=1
                    dbg(s)
                    if c>1:
                        quit()
                    if type(df_) == None:
                        print('NONE')
                        df_ = df[df[c].astype(str).str.contains(s, na = False)]
                    else:
                        df_ = pd.concat([df_, df[df[c].astype(str).str.contains(s, na = False)]])
                    print(set(df_['id']))
            df = df_
        elif operator == 'AND':
            for c,strs in in_col_select_rows.items():
                for s in strs:
                    df = df[df[c].astype(str).str.contains(s, na = False)]
    df.head(size).to_csv(sample_path, index = None, header=True)
    return sample_path

def get_csv_head(path, size=5, nrows=100):
    with open(path) as f:
        df = pd.read_csv(f, on_bad_lines='skip', nrows=nrows)
    if isinstance(size, str):
        return df
    return df.head(size)

def describe_csv(path):
    df = pd.read_csv(path, on_bad_lines='skip')
    return df.describe(include="all") #pd.DataFrame(df, c = df.shape),


from django.db import models

from users.models import ImagesUser

# Create your models here.

class Image(models.Model):
    name = models.CharField(max_length=20)
    owner = models.ForeignKey(ImagesUser, on_delete=models.CASCADE)
    file = models.ImageField(upload_to="images/")
    created_at = models.DateTimeField(auto_now_add=True)


import numpy as np
import pandas as pd
import shap
from ALEScore import *
def get_shap(model, X):
  explainerShap = shap.Explainer(model.predict, X, )
  shap_values = explainerShap(X, feature_perturbation = "tree_path_dependent")
  vector = []
  for i in range(X.shape[1]):
    value = abs(shap_values.values[:, i]).mean()
    vector.append(value)
  #print(shap.summary_plot(shap_values, X, plot_type="bar"))
  return vector

def get_shapTreeRegressor(model, X):
  explainerTree = shap.TreeExplainer(model, feature_perturbation = "tree_path_dependent").shap_values(X)
  vector = []
  for i in range(X.shape[1]):
    value = abs(explainerTree[:, i]).mean()
    vector.append(value)
  #print(shap.summary_plot(explainerTree, X, plot_type="bar"))
  #print("shap",vector)
  return vector

def get_shapTreeClassifier(model, X):
  explainerTree = shap.TreeExplainer(model,feature_perturbation = "tree_path_dependent" ).shap_values(X)
  #vector = []
  #for i in range(X.shape[1]):
    #if name == "RandomForestClassifier":
      #value = abs(explainerTree[0][:, i]).mean()
      #vector.append(value)
    #else:
      #value = abs(explainerTree[0][i]).mean()
      #vector.append(value)
  vector = np.mean(np.abs(explainerTree[0]), axis=0)
  #print("shap",vector)
  #print(shap.summary_plot(explainerTree, X, plot_type="bar"))
  return  vector

def get_FeatImport(model, X):
  vector = model.feature_importances_
  #print(plt.bar(range(X.shape[1]),model.feature_importances_))
  #print("pfi",vector)
  return vector

def get_ALE_max(model, X, y, names):
  if np.unique(y).shape[0] > 2:
      proba_fun = model.predict
  else:
      proba_fun = model.predict_proba
  proba_ale = ALE(proba_fun, feature_names=names, target_names=["y"])
  proba_exp = proba_ale.explain(X)
  vector = []
  for i in range(X.shape[1]):
    value = abs(proba_exp.ale_values[i][:,0]).max()
    #value = np.mean(proba_exp.ale_values[i][:,0])
    vector.append(value)
  #print("ale_1", vector)
  return vector

def get_ale(model, X, y):
  #X = pd.DataFrame(X)
  if np.unique(y).shape[0] > 2:
      predict_fun = model.predict
      #print("predict")
  else:
      predict_fun = model.predict_proba
      #print("proba")
  vector = []
  for i in range(X.shape[1]):
    ale = ALE2(X, y,model, i)
    vector.append(ale)
 # print("ale_2",vector)
  return (vector)

def get_coef(model):
  coefficients = model.coef_[0]
  vector = np.array(coefficients)
  #print("coef",vector)
  return vector

def adjust_predictions(y_pred, epsilon=1e-15):
  return np.clip(y_pred, epsilon, 1 - epsilon)


"""
2048 이지~
https://www.acmicpc.net/problem/12100
"""
def move(matrix, dir):
    global N
    change = False

    dx = [-1,1,0,0]
    dy = [0,0,-1,1]
    copy_matrix = [[0] * N for _ in range(N)]
    copy_matrix2 = [[0] * N for _ in range(N)]
    for i in range(N):
        for j in range(N):
            copy_matrix2[i][j] = matrix[i][j]

    for i in range(N):
        for j in range(N):
            tmp_dis = 0
            nx = i
            ny = j
            while 1:
                nx += dx[dir]
                ny += dy[dir]
                if nx in range(N) and ny in range(N):
                    tmp_dis += 1
                else:
                    copy_matrix[i][j] = tmp_dis
                    break

    #똑같은 애들합치는 작업
    for i in range(N):
        for j in range(N):
            if copy_matrix[i][j] == 0:
                ox, oy = i, j
                nx, ny = i, j
                while ox in range(N) and oy in range(N):
                    if copy_matrix2[ox][oy] != 0:
                        nx, ny = ox + (dx[dir] * (-1)), oy + (dy[dir] * (-1))
                        while nx in range(N) and ny in range(N):
                            if copy_matrix2[ox][oy] == copy_matrix2[nx][ny]:
                                copy_matrix2[ox][oy] *= 2
                                copy_matrix2[nx][ny] = 0
                                change = True
                                break
                            elif copy_matrix2[nx][ny] != 0 and copy_matrix2[ox][oy] != copy_matrix2[nx][ny]:
                                break
                            elif copy_matrix2[nx][ny] == 0:
                                nx += (dx[dir] * (-1))
                                ny += (dy[dir] * (-1))
                    ox += (dx[dir] * (-1))
                    oy += (dy[dir] * (-1))

    #합치고나서 0 된 애들을 찾아서 루프 돌려
    for i in range(N):
        for j in range(N):
            if copy_matrix[i][j] == 0:
                ox, oy = i, j

                while ox in range(N) and oy in range(N):
                    if copy_matrix2[ox][oy] == 0:
                        nx, ny = ox,oy
                        while nx in range(N) and ny in range(N):
                            if copy_matrix2[nx][ny] != 0:
                                copy_matrix2[ox][oy], copy_matrix2[nx][ny] = copy_matrix2[nx][ny], copy_matrix2[ox][oy]
                                change = True
                                break
                            nx += (dx[dir] * (-1))
                            ny += (dy[dir] * (-1))
                    ox += (dx[dir] * (-1))
                    oy += (dy[dir] * (-1))

    return [copy_matrix2,change]

def dfs(matrix, cost):
    global result
    if cost == 5:
        for i in range(N):
            for j in range(N):
                result = max(matrix[i][j], result)
    else:
        for i in range(4):
            tmp_matrix, change = move(matrix, i)
            if change:
                dfs(tmp_matrix, cost + 1)

N = int(input())
matrix = [[0] * (N) for _ in range(N)]
for i in range(N):
    matrix[i] = list(map(int, input().split()))

result = 0
cost = 0
if N == 1:
    print(matrix[0][0])
else:
    dfs(matrix, cost)
    print(result)


class Solution:
    def findDuplicate(self, paths: List[str]) -> List[List[str]]:
        groups = defaultdict(list)
        for line in paths:
            directory, *files = line.split()
            for file in files:
                name, content = file.split('(')
                groups[content].append(directory + '/' + name)
        return [contents for contents in groups.values() if len(contents) > 1]

src/609. Find Duplicate File in System.py