# -*- coding: utf-8 -*-
import numpy as np
import pickle
import os
import gc
from typing import Dict

curPath = os.path.abspath(os.path.dirname(__file__))
rootPath = os.path.split(curPath)[0] + "/Support/chapter02"
NBayesPath = rootPath + "/train_word_bag/NBayes.dat"

def writeBunch(path, bunch):
    fileObj = open(path, "wb")
    pickle.dump(bunch, fileObj)
    fileObj.close()

def loadDataSet():
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him', 'my'],
                   ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
                   ['mr', 'licks', 'ate', 'umy', 'steak', 'how', '84', '8stop', 'asawsx'],
                   ['mr554', 'licks', 'ate', 'umy', 'steak', 'how', 'ftdto', '8stop', 'asawsx'],
                   ['mr', 'licks', 'ate', 'umy', 'st55eak', 'how', 'ftdto', '8stop', 'asawsx'],
                   ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
    classVec = [0, 1, 0, 1, 3, 3, 3, 1]  # 1 is abusive, 0 not
    return postingList, classVec

# 写入bunch对象
def self(path, bunchobj):
    file_obj = open(path, "wb")
    pickle.dump(bunchobj, file_obj)
    file_obj.close()

isTFIDF = True

class ClassData(object):
    def __init__(self, classIndex, label, vocaLength, rate):
        self.label = label
        self.vocaLength = vocaLength
        self.classIndex = classIndex
        self.tfs = []
        self.rate = rate
        self.tdm = None

    def putTf(self, tf):
        self.tfs.append(tf)
        return self.tfs

    def calPXY(self, idf):
        self.tdm = np.zeros([1, self.vocaLength])
        for tf in self.tfs:
            self.tdm += np.multiply(tf, idf)
        if isTFIDF:
            self.tdm /= float(np.shape(self.tfs)[0])
        else:
            self.tdm /= float(np.shape(self.tfs)[0])

    def toPredict(self, testVec):

        if isTFIDF:
            weight = np.sum(np.multiply(self.tdm, testVec))  # 这种效果比上面好.上面的符合理论
        else:
            weight = np.sum(np.log(np.multiply(self.tdm, testVec) + 1)) * self.rate  # 朴素贝叶斯
        return weight

class NBayes(object):
    def __init__(self):
        self.vocabularys = []  # 词典
        self.vocabularyIndex = {}  # 字典表记录词典索引
        self.classMap = {}  # P(yi)--是个类别字典,结构Pcates[label]
        self.stopWordDict = None
        self.idf = None
        self.classIndex: Dict = None

    def makeTrain(self, trainMatrix, classVec):

        self.vocabularys = list(set([word for i in range(len(trainMatrix)) for word in trainMatrix[i]]))
        self.vocabularyIndex = {name: index for index, name in enumerate(self.vocabularys)}  # 索引序列

        self.makeClassMap(classVec)  # 计算每个分类在总类别集合中出现的概率：P(yi)
        if isTFIDF:
            self.calTfIdf(trainMatrix, classVec)  # 生成tf-idf权值
        else:
            self.calWordFreq(trainMatrix, classVec)  # 朴素贝叶斯

        gc.collect()
        self.makeTdm()  # 按分类累计向量空间的每维值：P(x|yi),X词在当前类别出现的次数/类别下所有词数(yi)

    # 生成 tf-idf
    def calTfIdf(self, trainMatrix, classVec):
        idf = np.zeros([len(self.classIndex), len(self.vocabularys)])

        for i in range(np.shape(trainMatrix)[0]):
            tf = np.zeros([1, len(self.vocabularys)])
            if i % 100 == 0:
                print("第", i + 1, "次运行")
            classData: ClassData = self.classMap[classVec[i]]
            for word in set(trainMatrix[i]):
                index = self.vocabularyIndex[word]
                tf[0, index] += trainMatrix[i].count(word)
                idf[self.classIndex[classVec[i]], index] += 1
            # 统计文本词频TF
            tf[0] = tf[0] / float(np.sum(tf[0]))
            classData.putTf(tf)
        self.idf = np.log(float(np.shape(trainMatrix)[0] + 1) / (np.sum(idf, axis=0)))  # 理论上要这个
        idf = idf.clip(max=1)
        idf = np.sum(idf, axis=0)
        self.idf *= np.log(float(len(self.classIndex) + 1) / (idf))  # 我加了这个

    """
    生成词频
    适配TFIDF
    """

    def calWordFreq(self, trainMatrix, classVec):
        self.idf = np.ones([1, len(self.vocabularys)])
        for i in range(np.shape(trainMatrix)[0]):
            tf = np.zeros([1, len(self.vocabularys)])
            if i % 100 == 0:
                print("第", i + 1, "次运行")
            classData: ClassData = self.classMap[classVec[i]]
            for word in set(trainMatrix[i]):
                index = self.vocabularyIndex[word]
                tf[0, index] += trainMatrix[i].count(word)
            tf[0, index] /= np.sum(tf[0, index])
            classData.putTf(tf)

    """
    生成分类字典,统计每个类别出现的概率P(yi)
    生成分类索引字典
    """

    def makeClassMap(self, classVec):
        classList = list(set(classVec))
        for index, label in enumerate(classList):  # 对分类进行去重遍历
            rate = float(classVec.count(label)) / float(np.alen(classVec))
            self.classMap[label] = ClassData(index, label, np.alen(self.vocabularys), rate)
        self.classIndex = {k: i for i, (k, v) in enumerate(self.classMap.items())}

    """
     按分类计算P(x|yi)
    """

    def makeTdm(self):
        print("构建分类权重")
        for label, classData in self.classMap.items():
            classData.calPXY(self.idf)
        print("分类权重构建完毕")

    def getDumpData(self):
        dictData = {}
        dictData["vocabularys"] = self.vocabularys
        dictData["vocabularyIndex"] = self.vocabularyIndex
        for label, classData in self.classMap.items():
            classData.tfs = None
        dictData["classMap"] = self.classMap
        dictData["stopWordDict"] = self.stopWordDict
        return dictData

    def loadDumpData(self, dictData):
        self.vocabularys = dictData["vocabularys"]
        self.vocabularyIndex = dictData["vocabularyIndex"]
        self.classMap = dictData["classMap"]
        self.stopWordDict = dictData["stopWordDict"]
        return self

    # 构建测试集合
    def makeTest(self):
        print("构造预测函数")

        def predict(testMatrix, nb: NBayes):
            print("准备预测")
            testMatrix = nb.deleteStopWord(testMatrix)
            testDetail = [{"matrixIndex": i, "all": 0, "notFind": 0} for i in range(np.shape(testMatrix)[0])]
            testTfMatrix = np.zeros([np.shape(testMatrix)[0], len(nb.vocabularys)])
            for i in range(np.shape(testTfMatrix)[0]):
                testDetail[i]["all"] = len(testMatrix[i])
                notFind = 0
                for word in testMatrix[i]:
                    index = nb.vocabularyIndex[word] if word in nb.vocabularyIndex else None
                    if index is not None:
                        testTfMatrix[i, index] += 1
                    else:
                        notFind += 1
                testDetail[i]["notFind"] = notFind

                testTfMatrix[i] = testTfMatrix[i] / float(np.sum(testTfMatrix[i]))

            testClassList = [0] * np.shape(testTfMatrix)[0]
            print("开始预测")
            for i in range(np.shape(testTfMatrix)[0]):
                predvalue = 0
                retClass = 0
                for label, classData in nb.classMap.items():
                    # P(x|yi)P(yi)
                    value = classData.toPredict(testTfMatrix[i])
                    if value > predvalue:
                        predvalue = value
                        retClass = label
                testClassList[i] = retClass
            print("预测结束")
            return testClassList, testDetail

        return predict

    """
    去除停用词
    """

    def deleteStopWord(self, matrix, stopWords=None):
        if (stopWords is not None):
            self.stopWordDict = set(stopWords)
            self.stopWordDict.add("")
            self.stopWordDict.add('\ufeff')
        print("正在删除停用词")
        for i in range(len(matrix)):
            for j in range(len(matrix[i]) - 1, -1, -1):
                word = matrix[i][j]
                if word in self.stopWordDict:
                    matrix[i].pop(j)

        print("停用词删除完毕")
        return matrix


import os
import argparse
import asyncio
import time
from datetime import timedelta

import logging, logging.handlers
_LOGGER = logging.getLogger(__name__)

import discord

EMOJI_ASTRAY = "\N{ANGRY FACE}"
EMOJI_ACTIVE = "\N{FACE WITH MONOCLE}"
EMOJI_FINISHED = "\N{SHRUG}\N{ZWJ}\N{FEMALE SIGN}\uFE0F"
EMOJI_IGNORED = "\N{POUTING FACE}"
EMOJI_SPECTRUM = frozenset({
    EMOJI_ASTRAY,
    EMOJI_ACTIVE,
    EMOJI_FINISHED,
    EMOJI_IGNORED,
})

QUEUE_RULES_PREFIX = """
В этом чате установлены следующие правила:
""".strip('\n')
QUEUE_RULES_PREFIX_OTHER = """
В чате очереди установлены следующие правила:
""".strip('\n')
QUEUE_RULES = f"""
\N{HEAVY CHECK MARK} в очередь нужно записаться, указав задачи, которые вы хотите сдать;
{EMOJI_IGNORED} нельзя писать несколько сообщений; можно редактировать или удалять старое;
{EMOJI_ASTRAY} записавшись, нужно подключиться к голосовому каналу очереди (с выключенным микрофоном);
{EMOJI_ACTIVE} свободный преподаватель переключит вас в свой канал для разговора;
{EMOJI_FINISHED} поcле разговора сообщение из очереди нужно удалить.
""".strip('\n')

QUEUE_ALGORITHM = f"""
Я буду отмечать сообщения учеников в текстовом чате очереди одной из реакций {EMOJI_IGNORED}, {EMOJI_ASTRAY}, {EMOJI_ACTIVE}, {EMOJI_FINISHED} (или ни одной из них). Наличие реакции означает, что сообщение нерелевантно по той или иной причине.

{EMOJI_IGNORED} это не первое сообщение ученика в очереди;
{EMOJI_ASTRAY} ученик не подключен ни к какому голосовому каналу;
{EMOJI_ACTIVE} ученик подключен (или его подключили) к некоторому голосовому каналу, кроме канала очереди;
{EMOJI_FINISHED} ученик подключался (или его подключали) к некоторому голосовому каналу, кроме канала очереди (если у ученика есть несколько сообщений в разных чатах очереди, будет отмечено только одно из них — по умолчанию в первом канале по списку).

Самостоятельно выйти из «прослушанного» состояния ({EMOJI_FINISHED}) ученик может, только удалив свое сообщение. Если преподаватель видит, что это состояние наступило некорректно (например, ученик случайно зашёл не в тот канал, или хотел сдавать определенному преподавателю), он может стереть все реакции у сообщения, вернув его к изначальному состоянию.

Сообщения от преподавателей игнорируются (кроме команд).

(Определения.
_Преподаватель_ — это участник сервера, у которого есть роль, название которой начинается с «препод» в любом регистре.
_Текстовый чат очереди_ — это любой текстовый чат, название которого начинается с «очередь» в любом регистре, либо находящийся в категории с подобным названием.
_Голосовой канал очереди_ — это любой голосовой канал, название которого начинается с «очередь» в любом регистре, либо находящийся в категории с подобным названием.)
""".strip('\n')

QUEUE_COMMANDS_SHORT = """
Команды: «команды», «правила», «алгоритм», «следующий».
""".strip('\n')

QUEUE_COMMANDS = """
Команды:
«команды» — список команд с описанием;
«правила» — краткие правила очереди;
«алгоритм» — более полное описание алгоритма;
«следующий» — в чате очереди, переместить первого ученика в очереди в голосовой канал к преподавателю.
""".strip('\n')

EDIT_RULES_ON_STARTUP = True

QUEUE_PREFIX = "очередь"
TEXT_QUEUE_PREFIX = QUEUE_PREFIX
VOICE_QUEUE_PREFIX = QUEUE_PREFIX
TEACHER_ROLE_PREFIX = "препод"

TIME_LIMIT_CLEAN  = 60 * 60 * 24 * 7
TIME_LIMIT_ACTIVE = 60 * 60
TIME_EPSILON = 10

class QueueBot(discord.Client):

    def __init__(self, *args, **kwargs):
        kwargs["intents"] = discord.Intents(
            guilds=True, members=True,
            messages=True, voice_states=True, reactions=True )
        super().__init__(*args, **kwargs)
        self.queue_states = dict()
        self.dict_lock = asyncio.Lock()
        self.student_activity = self.StudentActivity()

    async def setup_hook(self):
        self.student_activity.start_monitor()

    async def on_error(self, event, *args, **kwargs):
        def recover_guild(*args, **kwargs):
            return None
        if event == 'on_message':
            def recover_guild(message):
                return message.guild
        elif event == 'on_voice_state_update':
            def recover_guild(member, before, after):
                if isinstance(member, discord.Member):
                    return member.guild
        elif event == 'on_raw_reaction_add' or event == 'on_raw_reaction_remove':
            def recover_guild(payload):
                if payload.guild_id is not None:
                    return self.get_guild(payload.guild_id)
        try:
            guild = recover_guild(*args, **kwargs)
        except Exception as error:
            _LOGGER.exception( "Exception while determining guild in on_error",
                exc_info=True )
        prefix = f"Exception in {event}"
        if guild is not None:
            prefix += f" (guild “{guild.name}”)"
        _LOGGER.exception(prefix, exc_info=True)

    class QueueState:

        def __init__(self):
            self.messages = dict()
            self.finished = set()
            self.update()
            self.lock = asyncio.Lock()

        def update(self):
            self.mtime = time.monotonic()

    async def queue_state(self, guild, member):
        if not isinstance(member, int):
            if guild is None:
                guild = member.guild
            member = member.id
        if not isinstance(guild, int):
            guild = guild.id
        async with self.dict_lock:
            try:
                guild_states = self.queue_states[guild]
            except KeyError:
                guild_states = self.queue_states[guild] = dict()
            try:
                state = guild_states[member]
            except KeyError:
                state = guild_states[member] = self.QueueState()
                asyncio.get_running_loop().create_task(
                    self.clean_queue_state(guild, member, state) )
            return state

    async def clean_queue_state(self, guild_id, member_id, queue_state):
        while True:
            delta = queue_state.mtime + TIME_LIMIT_CLEAN - time.monotonic()
            if delta > 0:
                await asyncio.sleep(delta + TIME_EPSILON)
                continue
            break
        async with self.dict_lock:
            if guild_id not in self.queue_states:
                return
            guild_states = self.queue_states[guild_id]
            if member_id not in guild_states:
                return
            if guild_states[member_id] is not queue_state:
                return
            del guild_states[member_id]
            if not guild_states:
                del self.queue_states[guild_id]
        await self.vandalize_queue_state( guild_id, member_id,
            queue_state )

    async def vandalize_queue_state(self, guild_id, member_id, queue_state):
        async with queue_state.lock:
            guild = self.get_guild(guild_id)
            if guild is None:
                return
            member = guild.get_member(member_id)
            if member is None:
                return
            for channel_id, message_id in queue_state.messages.items():
                channel = guild.get_channel(channel_id)
                if channel is None:
                    continue
                try:
                    message = await channel.fetch_message(message_id)
                except (discord.NotFound, discord.Forbidden):
                    continue
                await self.message_add_reactions(message, {EMOJI_IGNORED})
            queue_state.messages.clear()
            queue_state.finished.clear()

    class StudentActivity(dict):

        def __init__(self):
            self.awaken = asyncio.Event()
            self.wake_task = None
            self.wake_time = None

        def start_monitor(self):
            asyncio.get_running_loop().create_task(self.monitor())

        async def monitor(self):
            while True:
                await self.awaken.wait()
                self.awaken.clear()
                self.wake_task = self.wake_time = None
                now = time.monotonic()
                gone_inactive = set()
                deltas = list()
                for guild, atime in self.items():
                    delta = atime + TIME_LIMIT_ACTIVE - now
                    if delta <= 0:
                        gone_inactive.add(guild)
                    else:
                        deltas.append(delta)
                for guild in gone_inactive:
                    del self[guild]
                    self.report_inactive(guild)
                if deltas:
                    self.delay_wake(duration=min(deltas) + TIME_EPSILON)
                else:
                    self.report_silence()

        def report_silence(self):
            _LOGGER.info(
                f"All servers have gone inactive" )

        def report_active(self, guild):
            _LOGGER.info(
                f"Server “{guild.name}” (id={guild.id}) has become active" )

        def report_inactive(self, guild):
            guild_name = guild.name
            if guild_name is None:
                guild_name = "<unavailable>"
            _LOGGER.info(
                f"Server “{guild_name}” (id={guild.id}) has gone inactive" )

        def note_guild(self, guild, mtime=None):
            fresh = guild not in self
            now = time.monotonic()
            if mtime is not None:
                age = (discord.utils.utcnow() - mtime).total_seconds()
                if age >= TIME_LIMIT_ACTIVE:
                    return
                now -= age
            if fresh:
                self.report_active(guild)
            self[guild] = now
            if fresh:
                self.delay_wake( now=now,
                    duration=TIME_LIMIT_ACTIVE + TIME_EPSILON )

        def clear_guild(self, guild):
            if guild in self:
                del self[guild]
                self.report_inactive(guild)

        def delay_wake(self, *, now=None, duration):
            if now is None:
                now = time.monotonic()
            wake_time = now + duration
            if self.wake_task is not None:
                if self.wake_time <= wake_time:
                    return
                self.wake_task.cancel()
                self.wake_task = self.wake_time = None
            self.wake_task = asyncio.get_running_loop().create_task(
                self.wait_and_wake(duration) )
            self.wake_time = wake_time

        async def wait_and_wake(self, duration):
            await asyncio.sleep(duration)
            self.awaken.set()

    def member_is_teacher(self, member):
        if not isinstance(member, discord.Member):
            return False
        return any(
            role.name.lower().startswith(TEACHER_ROLE_PREFIX)
            for role in member.roles )

    def text_channel_is_queue(self, text_channel):
        if text_channel.name.lower().startswith(TEXT_QUEUE_PREFIX):
            return True
        category = text_channel.category
        if category is not None and \
                category.name.lower().startswith(QUEUE_PREFIX):
            return True
        return False

    def voice_channel_is_queue(self, voice_channel):
        if voice_channel.name.lower().startswith(VOICE_QUEUE_PREFIX):
            return True
        category = voice_channel.category
        if category is not None and \
                category.name.lower().startswith(QUEUE_PREFIX):
            return True
        return False

    def queue_channels(self, guild):
        for text_channel in guild.text_channels:
            if self.text_channel_is_queue(text_channel):
                yield text_channel

    async def consider_message( self, message, *,
        guild=None, channel=None, member=None,
        queue_state=None, lock_acquired=False,
        historical=False,
    ):
        # return whether something has changed
        if member is None:
            member = message.author
            if not isinstance(member, discord.Member):
                return False
            if member == self.user:
                return False
            if self.member_is_teacher(member):
                return False
        if channel is None:
            channel = message.channel
            if not isinstance(channel, discord.TextChannel):
                return False
            if not self.text_channel_is_queue(channel):
                return False
        if guild is None:
            guild = member.guild
            assert guild == channel.guild
        if queue_state is None:
            if lock_acquired:
                raise RuntimeError("escaping potential deadlock")
            queue_state = await self.queue_state(guild, member)
        if not lock_acquired:
            await queue_state.lock.acquire()
        try:
            messages = queue_state.messages
            finished = queue_state.finished
            emoji = set( reaction.emoji
                for reaction in message.reactions
                if reaction.emoji in EMOJI_SPECTRUM )
            if EMOJI_IGNORED in emoji:
                if channel.id in messages and \
                        messages[channel.id] == message.id:
                    del messages[channel.id]
                    finished.discard(message.id)
                    queue_state.update()
                    return True
                return False
            if channel.id in messages:
                if messages[channel.id] == message.id:
                    if EMOJI_ACTIVE not in emoji:
                        if EMOJI_FINISHED in emoji:
                            if message.id not in finished:
                                finished.add(message.id)
                                queue_state.update()
                                self.student_activity.note_guild(guild)
                                return True
                        else:
                            if message.id in finished:
                                finished.discard(message.id)
                                queue_state.update()
                                self.student_activity.note_guild(guild)
                                return True
                    return False
                old_message_id = messages[channel.id]
                try:
                    old_message = await channel.fetch_message(old_message_id)
                except (discord.NotFound, discord.Forbidden):
                    old_message = None
                if old_message is None:
                    del messages[channel.id]
                    finished.discard(old_message_id)
                elif old_message_id in finished:
                    del messages[channel.id]
                    finished.discard(old_message_id)
                    await self.message_ignore(old_message)
                else:
                    await self.message_add_reactions(message, {EMOJI_IGNORED})
                    return False
            messages[channel.id] = message.id
            if EMOJI_FINISHED in emoji:
                finished.add(message.id)
            queue_state.update()
            if not historical:
                self.student_activity.note_guild(guild)
            else:
                self.student_activity.note_guild( guild,
                    mtime=message.created_at )
            return True
        finally:
            if not lock_acquired:
                queue_state.lock.release()

    async def update_student( self, member, *,
        guild=None, voice=None, voice_arg=False, allow_finish=False,
        queue_state=None, lock_acquired=False,
    ):
        if guild is None:
            guild = member.guild
        if queue_state is None:
            if lock_acquired:
                raise RuntimeError("escaping potential deadlock")
            queue_state = await self.queue_state(guild, member)
        if not lock_acquired:
            await queue_state.lock.acquire()
        try:
            messages = queue_state.messages
            finished = queue_state.finished
            if not voice_arg:
                voice = member.voice
            if voice is None or voice.channel is None:
                status = "astray"
            elif self.voice_channel_is_queue(voice.channel):
                status = "normal"
            else:
                status = "active"
            prospective_finished = set()
            if status == "active":
                if not finished and allow_finish:
                    for channel in self.queue_channels(guild):
                        if channel.id not in messages:
                            continue
                        prospective_finished.add(messages[channel.id])
                        break
                self.student_activity.note_guild(guild)
            garbage = list()
            for channel_id, message_id in messages.items():
                channel = guild.get_channel(channel_id)
                if channel is None or not self.text_channel_is_queue(channel):
                    garbage.append((channel_id, message_id))
                    continue
                try:
                    message = await channel.fetch_message(message_id)
                except (discord.NotFound, discord.Forbidden):
                    garbage.append((channel_id, message_id))
                    continue
                emoji = set()
                if message_id in prospective_finished:
                    finished.add(message_id)
                if status == "active":
                    emoji.add(EMOJI_ACTIVE)
                elif message_id in finished:
                    emoji.add(EMOJI_FINISHED)
                elif status == "astray":
                    emoji.add(EMOJI_ASTRAY)
                await self.message_add_reactions(message, emoji)
            for channel_id, message_id in garbage:
                del messages[channel_id]
                finished.discard(message_id)
            queue_state.update()
        finally:
            if not lock_acquired:
                queue_state.lock.release()

    async def on_ready(self):
        _LOGGER.info(
            f"{self.user} has connected to Discord, "
            f"and manages queues on {len(self.guilds)} servers" )

    async def on_guild_available(self, guild):
        me = guild.me
        _LOGGER.info(
            f"Server “{guild.name}” (id={guild.id}) is available, and "
            f"I am “{me.nick if me.nick is not None else me.name}”" )
        await self.reconsider_guild(guild)

    on_guild_join = on_guild_available

    async def on_guild_unavailable(self, guild):
        guild_name = guild.name
        if guild_name is None:
            guild_name = "<unavailable>"
        _LOGGER.info(
            f"Server “{guild_name}” (id={guild.id}) is unavailable" )
        async with self.dict_lock:
            if guild.id in self.queue_states:
                del self.queue_states[guild.id]
        self.student_activity.clear_guild(guild)

    async def reconsider_guild(self, guild):
        for channel in self.queue_channels(guild):
            await self.reconsider_channel(channel, guild=guild)
        async with self.dict_lock:
            if guild.id not in self.queue_states:
                return
            for member_id, queue_state in self.queue_states[guild.id].items():
                member = guild.get_member(member_id)
                await self.update_student( member, guild=guild,
                    queue_state=queue_state )

    async def reconsider_channel(self, channel, *, guild):
        try:
            prehistoric = discord.utils.utcnow() - timedelta(TIME_LIMIT_CLEAN)
            prehistoric_limit = 7
            async for message in channel.history(limit=None):
                if EDIT_RULES_ON_STARTUP and message.author == self.user:
                    content = message.content
                    if not content.startswith(QUEUE_RULES_PREFIX):
                        continue
                    if content == QUEUE_RULES_PREFIX + "\n" + QUEUE_RULES:
                        continue
                    _LOGGER.info("editing previously posted rules")
                    await message.edit(
                        content=QUEUE_RULES_PREFIX + "\n" + QUEUE_RULES )
                    prehistoric_limit = 0
                    continue
                if message.created_at <= prehistoric:
                    prehistoric_limit -= 1
                    if prehistoric_limit < 0:
                        break
                await self.consider_message( message,
                    guild=guild, channel=channel,
                    historical=True )
        except discord.Forbidden:
            pass

    async def on_message(self, message):
        member = message.author
        channel = message.channel
        if member == self.user:
            return
        if not isinstance(channel, discord.TextChannel):
            return
        if self.user in message.mentions:
            if await self.on_command(message):
                return
        if self.member_is_teacher(member):
            return
        if not self.text_channel_is_queue(channel):
            return
        queue_state = await self.queue_state(None, member)
        async with queue_state.lock:
            changed = await self.consider_message( message,
                member=member, channel=channel,
                queue_state=queue_state, lock_acquired=True )
            if changed:
                await self.update_student( member,
                    queue_state=queue_state, lock_acquired=True )

    async def on_voice_state_update(self, member, before, after):
        if self.member_is_teacher(member):
            return
        if before.channel == after.channel:
            return
        await self.update_student(member, voice=after, allow_finish=True)

    async def on_raw_reaction_add(self, payload):
        if payload.user_id == self.user.id:
            return
        if payload.guild_id is None:
            return
        guild = self.get_guild(payload.guild_id)
        if guild is None:
            return
        channel = guild.get_channel(payload.channel_id)
        if channel is None or not isinstance(channel, discord.TextChannel):
            return
        if not self.text_channel_is_queue(channel):
            return
        try:
            message = await channel.fetch_message(payload.message_id)
        except (discord.NotFound, discord.Forbidden):
            return
        member = message.author
        if member == self.user:
            return
        if self.member_is_teacher(member):
            return
        queue_state = await self.queue_state(guild, member)
        async with queue_state.lock:
            changed = await self.consider_message( message,
                member=member, channel=channel,
                queue_state=queue_state, lock_acquired=True )
            if changed:
                await self.update_student( member,
                    queue_state=queue_state, lock_acquired=True )

    async def on_raw_reaction_remove(self, payload):
        if payload.guild_id is None:
            return
        guild = self.get_guild(payload.guild_id)
        if guild is None:
            return
        channel = guild.get_channel(payload.channel_id)
        if channel is None or not isinstance(channel, discord.TextChannel):
            return
        if not self.text_channel_is_queue(channel):
            return
        try:
            message = await channel.fetch_message(payload.message_id)
        except (discord.NotFound, discord.Forbidden):
            return
        member = message.author
        if member == self.user:
            return
        if self.member_is_teacher(member):
            return
        queue_state = await self.queue_state(guild, member)
        async with queue_state.lock:
            changed = await self.consider_message( message,
                member=member, channel=channel,
                queue_state=queue_state, lock_acquired=True )
            if changed:
                await self.update_student( member,
                    queue_state=queue_state, lock_acquired=True )

    on_raw_reaction_clear_emoji = on_raw_reaction_remove
    on_raw_reaction_clear = on_raw_reaction_remove

    async def message_ignore(self, message):
        try:
            await message.add_reaction(EMOJI_IGNORED)
        except (discord.NotFound, discord.Forbidden):
            return

    async def message_add_reactions(self, message, emoji_set):
        try:
            if not emoji_set <= EMOJI_SPECTRUM:
                unknown_emoji = next(emoji_set - EMOJI_SPECTRUM)
                raise RuntimeError(
                    f"Unrecognised emoji {unknown_emoji} "
                    f"(code {''.join(hex(ord(e)) for ee in unknown_emoji)})" )
            me = self.user
            emoji_add = set(emoji_set)
            reactions_remove = list()
            for reaction in message.reactions:
                if reaction.emoji not in EMOJI_SPECTRUM:
                    continue
                if reaction.emoji in emoji_set:
                    async for user in reaction.users():
                        if user == me:
                            emoji_add.discard(reaction.emoji)
                            break
                else:
                    reactions_remove.append(reaction)
            for emoji in emoji_add:
                await message.add_reaction(emoji)
            for reaction in reactions_remove:
                await reaction.clear()
        except (discord.NotFound, discord.Forbidden):
            return

    async def on_command(self, message):
        # return whether it really should be considered a command
        if not self.member_is_teacher(message.author):
            return False
        channel = message.channel
        if not isinstance(channel, discord.TextChannel):
            return False
        mention_prefix = f"<@!{self.user.id}> "
        if message.content.startswith(mention_prefix):
            command = message.content[len(mention_prefix):]
        else:
            await self.send_help( channel,
                reply_to=message.author, error="Команда не распознана." )
            return True
        if command.lower() == "команды":
            await self.send_help(channel, short=False)
        elif command.lower() == "правила":
            await self.on_command_rules( channel,
                is_queue=self.text_channel_is_queue(channel) )
        elif command.lower() == "алгоритм":
            await self.on_command_algorithm(channel)
        elif command.lower() == "следующий":
            if not self.text_channel_is_queue(channel):
                await channel.send( f"<@!{message.author.id}> "
                    "Команду «следующий» можно запускать "
                    "только в чате очереди.",
                    allowed_mentions=discord.AllowedMentions(
                        everyone=False, roles=False,
                        users=[message.author] )
                )
                return True
            await self.on_command_next(channel, message.author)
            await message.delete()
        else:
            await self.send_help( channel,
                reply_to=message.author,
                error=f"Команда «{command}» не распознана" )
        return True

    async def on_command_rules(self, channel, *, is_queue=False):
        #nick = channel.guild.me.nick
        prefix = QUEUE_RULES_PREFIX if is_queue else QUEUE_RULES_PREFIX_OTHER
        await channel.send( prefix + "\n" + QUEUE_RULES,
            allowed_mentions=discord.AllowedMentions(
                everyone=False, roles=False, users=False )
        )

    async def on_command_algorithm(self, channel):
        await channel.send( QUEUE_ALGORITHM,
            allowed_mentions=discord.AllowedMentions(
                everyone=False, roles=False, users=False )
        )

    async def on_command_next(self, channel, teacher):
        guild = channel.guild
        async for message in channel.history(oldest_first=True):
            member = message.author
            if not isinstance(member, discord.Member):
                continue
            if member == self.user:
                continue
            if self.member_is_teacher(member):
                continue
            queue_state = await self.queue_state(guild, member)
            if channel.id not in queue_state.messages:
                continue
            if queue_state.messages[channel.id] != message.id:
                continue
            if message.id in queue_state.finished:
                continue
            if member.voice is None or member.voice.channel is None:
                return
            break
        else:
            return
        async with queue_state.lock:
            if teacher.voice is None or teacher.voice.channel is None:
                return
            voice_channel = teacher.voice.channel
            if self.voice_channel_is_queue(voice_channel):
                return
            await member.move_to(voice_channel, reason="queue")
            queue_state.finished.add(message.id)

    async def send_help( self, channel,
        *, reply_to=None, error=None, short=True,
    ):
        sentences = []
        if reply_to is not None:
            sentences.append(f"<@!{reply_to.id}>")
        if error is not None:
            sentences.append(error)
        sentences.append(
            f"Формат команд: `@{channel.guild.me.name} <команда>`." )
        sentences.append(QUEUE_COMMANDS_SHORT if short else QUEUE_COMMANDS)
        await channel.send( " ".join(sentences),
            allowed_mentions=discord.AllowedMentions(
                everyone=False, roles=False,
                users=[reply_to] if reply_to is not None else False )
        )

async def main():
    parser = argparse.ArgumentParser("QueueBot for Discord")
    parser.add_argument("--log-level", choices=["DEBUG", "INFO", "WARNING"], default="INFO")
    parser.add_argument("--log-file")
    args = parser.parse_args()
    _LOGGER.setLevel(args.log_level)
    log_formatter = logging.Formatter(
        "%(asctime)s %(message)s",
        "%Y-%m-%d %H:%M:%S" )
    if args.log_file is None:
        log_stream_handler = logging.StreamHandler()
        _LOGGER.addHandler(log_stream_handler)
        log_stream_handler.setFormatter(log_formatter)
    else:
        log_stream_handler = logging.StreamHandler()
        log_stream_handler.setLevel("ERROR")
        log_stream_handler.setFormatter(log_formatter)
        _LOGGER.addHandler(log_stream_handler)
        log_file_handler = logging.handlers.RotatingFileHandler(
            args.log_file,
            maxBytes=(1 << 18), backupCount=1 )
        def antirotator(source, dest):
            with open(source, "wb"):
                pass
        log_file_handler.rotator = antirotator
        log_file_handler.setFormatter(log_formatter)
        _LOGGER.addHandler(log_file_handler)
    _LOGGER.info("starting up")
    _LOGGER.debug("debug output enabled")
    client = QueueBot()
    await client.start(os.getenv('DISCORD_TOKEN'))
    _LOGGER.info("shutting down")

if __name__ == '__main__':
    asyncio.run(main())

# vim: set wrap lbr :


##################
#  Imports & Co  #
##################

import random

import json

with open("cards_deck.json") as cards_deck:
    cards_data = json.load(cards_deck)

# Check deck length (uncomment for test only)
# print("lenght of deck:" + str(len(cards_data)))

##################
#   Game Setup   #
##################

# New class to handle game mechanics
class Game:
    def __init__(self, player1, player2):
        self.player1 = player1
        self.player2 = player2
        self.active_player = self.player1

        # Create players' town instances
        self.player1_town = Town(player1, self)
        self.player2_town = Town(player2, self)

        self.action_menu()
        self.process_user_input()

    def switch_player(self):
        if self.active_player == self.player1:
            self.active_player = self.player2
        else:
            self.active_player = self.player1

    # Display possible player's actions
    def action_menu(self):
        print(f"\n{self.active_player}, what do you wanna do next?")
        print("1. Display Town's Dashboard")
        print("2. Display cards in hand")
        # print("3. Place a worker")
        print("4. Stop playing")

    # Process player's choice depending on active player
    def process_user_input(self):
        try:
            # check if user input can be converted to integer
            user_input = int(input(f"{self.active_player}, choose an action: "))

        except ValueError as ve:
            # If input is not an integer, show exception to end-user and ask for new input
            ve_clean = str(ve).split(":")[-1].strip()
            print(
                f"{ve_clean} is not a valid choice. Please input the number of the action you wanna do."
            )
            self.action_menu()
            self.process_user_input()

        else:
            if user_input == 1:
                if self.active_player == self.player1:
                    self.player1_town.town_dashboard()
                else:
                    self.player2_town.town_dashboard()
            elif user_input == 2:
                if self.active_player == self.player1:
                    if len(self.player1_town.cards_in_hand) > 0:
                        cards_hand_display = f"\n{self.active_player}, here are the cards you have in hand:\n"
                        for (
                            card,
                            description,
                        ) in self.player1_town.cards_in_hand.items():
                            cards_hand_display += f"- {card.upper()}: {description}\n"
                        print(cards_hand_display)
                        self.player1_town.play_card(
                            input(
                                f"{self.active_player}, which card do you wanna play? "
                            )
                        )
                    else:
                        print(
                            f"{self.active_player}, you don't have any cards in your hand."
                        )
                        self.action_menu()
                        self.process_user_input()
                if self.active_player == self.player2:
                    if len(self.player2_town.cards_in_hand) > 0:
                        cards_hand_display = f"\n{self.active_player}, here are the cards you have in hand:\n"
                        for (
                            card,
                            description,
                        ) in self.player2_town.cards_in_hand.items():
                            cards_hand_display += f"- {card.upper()}: {description}\n"
                        print(cards_hand_display)
                        self.player2_town.play_card(
                            input(
                                f"{self.active_player}, which card do you wanna play? "
                            )
                        )
                    else:
                        print(
                            f"{self.active_player}, you don't have any cards in your hand."
                        )
                        self.action_menu()
                        self.process_user_input()
            # Not available yet
            # elif user_input == 3:
            #     self.place_worker()
            elif user_input == 4:
                exit()
            else:
                print(
                    f"There is no action with number {user_input}. Please input the number of the action you wanna do."
                )
                self.action_menu()
                self.process_user_input()

class Town:
    def __init__(self, name, game):
        self.name = name
        self.game = game
        self.workers = 2
        self.total_points = 0
        self.constructions = []
        self.critters = []
        self.resources = {"Twig": 0, "Resin": 0, "Pebble": 0, "Berry": 0}
        self.cards_in_hand = {}

        self.first_cards_gen(cards_data)

    # Simplified repr to stick to Python's principles
    def __repr__(self):
        town_repr = (
            f"In {self.name}'s town, there are currently {len(self.constructions)} constructions and {len(self.critters)} critters.\n"
            f"{self.name} has {self.total_points} Victory Points."
        )

        return town_repr

    # Dedicated method to display Town's Dashboard
    def town_dashboard(self):
        town_dashboard = (
            f"\nIn {self.name}'s town, there are currently {len(self.constructions)} constructions and {len(self.critters)} critters.\n"
            f"It represents {self.total_points} Victory Points.\n"
            f"There are also {self.workers} workers and the following resources available:\n"
        )

        # This could be handled a different way through a dedicated global function maybe?
        # Could then use it in every sentences... -_-;
        for resource, quantity in self.resources.items():
            if quantity > 1 and resource == "Berry":
                town_dashboard += f"- {quantity} Berries\n"
            elif quantity > 1 and not resource == "Berry":
                town_dashboard += f"- {quantity} {resource}s\n"
            else:
                town_dashboard += f"- {quantity} {resource}\n"

        print(town_dashboard)
        self.game.action_menu()
        self.game.process_user_input()

    # For this version of the game, each player will get cards only once
    def first_cards_gen(self, cards_data):
        # Deal 5 cards to each player
        for cards_count in range(5):
            if len(cards_data) > 0:
                # Randomize and pop cards from deck
                i = random.randint(0, len(cards_data) - 1)
                card = Card(
                    cards_data[i]["deck"],
                    cards_data[i]["name"],
                    cards_data[i]["type"],
                    cards_data[i]["cost"],
                    cards_data[i]["points"],
                    cards_data[i]["unique"],
                )
                self.cards_in_hand[cards_data[i]["name"]] = card
                cards_data.pop(i)

        # return cards_in_hand

    # Check if played card is valid, remove it from player's hand and add it to town
    def play_card(self, card):
        if card.lower() in self.cards_in_hand:
            print(f"\n{self.name}, you just played {card.upper()}.")
            played_card = self.cards_in_hand[card.lower()]
            # Checking type of card to add to the right category
            if played_card.deck == "critter":
                self.critters.append(self.cards_in_hand.pop(card.lower()))
            elif played_card.deck == "construction":
                self.constructions.append(self.cards_in_hand.pop(card.lower()))
            # Adding card's points to Town's Victory Points
            self.total_points += played_card.points
            print(
                f"You now have {len(self.constructions)} constructions and {len(self.critters)} critters in your town. It represents {self.total_points} Victory Points."
            )
        else:
            print("You don't have this card in your hand.")
            self.play_card(input("Please choose another card: "))
        self.game.switch_player()
        self.game.action_menu()
        self.game.process_user_input()

# Generic class to handle any type of cards
class Card:
    def __init__(self, deck, name, type, cost, points, unique=False):
        self.deck = deck
        self.name = name
        self.type = type
        self.cost = cost
        self.points = points
        self.unique = unique

    def __repr__(self):
        card_repr = f"The {self.name.title()} is a {'unique' if self.unique else ''} {self.deck.title()}. It is a {self.type} card that gives {self.points} Victory Points. It costs: "

        # definitely needs a global function to handle plural
        for item in self.cost:
            quantity = item["quantity"]
            resource = item["resource"]
            card_repr += f"- {quantity} {resource.title()} "

        return card_repr

##################
#  Main program  #
##################

intro_logo = r"""
░█▀█░█░█░▀█▀░█░█░█▀█░█▀█░░░█▀▀░█░█░█▀▀░█▀▄░█▀▄░█▀▀░█░░░█░░
░█▀▀░░█░░░█░░█▀█░█░█░█░█░░░█▀▀░▀▄▀░█▀▀░█▀▄░█░█░█▀▀░█░░░█░░
░▀░░░░▀░░░▀░░▀░▀░▀▀▀░▀░▀░░░▀▀▀░░▀░░▀▀▀░▀░▀░▀▀░░▀▀▀░▀▀▀░▀▀▀
"""
print(intro_logo)
player1_name = input("Ready to play Python Everdell?\nWhat is your name? ")
player2_name = input(
    "\nWelcome " + str(player1_name) + "!\nWhat is your opponent's name? "
)
current_game = Game(player1_name, player2_name)


import pandas as pd

import numpy as np

import matplotlib.pyplot as plt
df_oof = pd.read_csv('../input/rnaoutputanalysis/holdouts.csv')
df_oof
df_oof['seq'] = df_oof.id_seqpos.apply(lambda v: v.split('_')[-2])

df_oof['i'] = df_oof.id_seqpos.apply(lambda v: int(v.split('_')[-1]))

df_oof = df_oof.sort_values('i')
df_oof = df_oof[df_oof.SN_filter == 1]
df_oof_react = df_oof[['seq', 'reactivity']].groupby('seq').reactivity.apply(list).reset_index()

df_oof_react
np.stack(df_oof_react.reactivity.values).shape
fig, ax = plt.subplots(1, 1, figsize=(30, 10))

oof_labels_filtered = np.stack(df_oof_react.reactivity.values)

for i in range(oof_labels_filtered.shape[0]):

    ax.plot(oof_labels_filtered[i, :], alpha=0.008, c='blue');

    ax.set_title('ractivity (holdout)')

ax.plot(np.mean(oof_labels_filtered, axis=0), alpha=1, c='darkblue', linewidth=2);

ax.plot(np.median(oof_labels_filtered, axis=0), alpha=1, c='darkblue', linewidth=2, linestyle='dashed');

ax.set_ylim(0, 4)
# EDA

df_train = pd.read_json('../input/stanford-covid-vaccine/train.json', lines=True)
fig, ax = plt.subplots(1, 1, figsize=(30, 10))

train_labels_filtered = np.stack(df_train[df_train.SN_filter.values == 1].reactivity.values)

for i in range(train_labels_filtered.shape[0]):

    ax.plot(train_labels_filtered[i, :], alpha=0.008, c='green');

    ax.set_title('reactivity (train)')

ax.plot(np.mean(train_labels_filtered[:, :], axis=0), alpha=1, c='darkgreen', linewidth=2);

ax.plot(np.median(train_labels_filtered[:, :], axis=0), alpha=1, c='darkgreen', linewidth=2, linestyle='dashed');

ax.set_ylim(0, 4)
from __future__ import print_function

from ipywidgets import interact, interactive, fixed, interact_manual

import ipywidgets as widgets
@interact(i=widgets.IntSlider(min=0, max=train_labels_filtered.shape[0]-1, step=1, value=0))

def show(i):

    plt.figure(figsize=(30, 3))

    plt.plot(train_labels_filtered[i, :], c='blue');

    plt.plot(oof_labels_filtered[i, :], c='green');

    plt.xlim(0, 107)

    plt.gca().set_xticks(np.arange(107))

    plt.gca().set_xticklabels(df_train.iloc[i].sequence)

    a = plt.gca().twiny()

    a.set_xticks(np.arange(107))

    a.set_xticklabels(df_train.iloc[i].structure)

    a.get_yaxis().set_visible(False)

    plt.grid()
# show residuals

fig, ax = plt.subplots(1, 1, figsize=(30, 10))

residuals = oof_labels_filtered - train_labels_filtered

for i in range(residuals.shape[0]):

    ax.plot(residuals[i, :], alpha=0.01, c='red');

    ax.set_title('residuals')

ax.plot(np.mean(residuals[:, :], axis=0), alpha=1, c='darkred', linewidth=2);

ax.plot(np.median(residuals[:, :], axis=0), alpha=1, c='darkred', linewidth=2, linestyle='dashed');

ax.set_ylim(-1, 1)
np.mean(residuals[:, :].flatten())
np.median(residuals[:, :].flatten())


from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

import time

##########################################################

cookie_id = "cookie"
cursor_id = "buyCursor"
cursor_xpath = '//*[@id="buyCursor"]/b/text()[2]'
grandma_id = "buyGrandma"
grandma_xpath = '//*[@id="buyGrandma"]/b/text()[2]'
factory_id = "buyFactory"
factory_xpath = '//*[@id="buyFactory"]/b/text()[2]'
mine_id = "buyMine"
mine_xpath = '//*[@id="buyMine"]/b/text()[2]'
shipment_id = "buyShipment"
shipment_xpath = '//*[@id="buyShipment"]/b/text()[2]'
alchemy_id = "buyAlchemy lab"
alchemy_path = '//*[@id="buyAlchemy lab"]/b/text()[2]'
portal_id = "buyPortal"
portal_xpath = '//*[@id="buyPortal"]/b/text()[2]'
time_machine_id = "buyTime machine"
time_machine_xpath = '//*[@id="buyTime machine"]/b/text()[2]'
cookie_amount_id = "money"
cps_id = "cps"

##########################################################

options = webdriver.ChromeOptions()
options.add_experimental_option("detach", True)

CHROME_DRIVER_PATH = "/Users/bruce/Dev/chromedriver"
s=Service(CHROME_DRIVER_PATH)
driver = webdriver.Chrome(service=s, options=options)
driver.get("https://orteil.dashnet.org/experiments/cookie/")

##########################################################

cookie = driver.find_element(By.ID, cookie_id)

cursor= driver.find_element(By.ID, cursor_id)

grandma = driver.find_element(By.ID, grandma_id)

factory = driver.find_element(By.ID, factory_id)

mine = driver.find_element(By.ID, mine_id)

shipment = driver.find_element(By.ID, shipment_id)

alchemy = driver.find_element(By.ID, alchemy_id)

portal = driver.find_element(By.ID, portal_id)

time_machine = driver.find_element(By.ID, time_machine_id)

# print(time_machine_price)

##########################################################

#LOOP

# timeout variable can be omitted, if you use specific value in the while condition
timeout = time.time() + 5   # [seconds]
one_min= time.time() + 60

timeout_start = time.time()
play = True
times = 0

cps = driver.find_element(By.ID, cps_id)
item_ids = ["buyCursor", "buyGrandma", "buyFactory", "buyMine", "buyShipment", "buyAlchemy lab",
            "buyPortal", "buyTime machine"]

#############################################
while True:
    cookie.click()

    if time.time() > timeout:

        all_prices = driver.find_elements(By.CSS_SELECTOR, "#store b")
        item_prices = []

        for price in all_prices:
            element_text = price.text
            print(element_text)
            if element_text != "":
                cost = int(element_text.split("-")[1].strip().replace(",", ""))
                item_prices.append(cost)

        cookie_upgrades = {}
        for n in range(len(item_prices)):
            cookie_upgrades[item_prices[n]] = item_ids[n]
        print(cookie_upgrades)

        cookie_money = driver.find_element(By.ID, cookie_amount_id).text
        if "," in cookie_money:
            cookie_money = cookie_money.replace(",", "")
        cookie_count = int(cookie_money)
        print(cookie_count)

        affordable_upgrades = {}
        for key, value in cookie_upgrades.items():
            if cookie_count > key:
                affordable_upgrades[key] = value
        print(cookie_upgrades.items())
        print(affordable_upgrades)

        highest_price_affordable_upgrade = max(affordable_upgrades)

        to_purchase_id = affordable_upgrades[highest_price_affordable_upgrade]

        driver.find_element(By.ID, to_purchase_id).click()

        timeout = time.time() + 5

    if time.time() >one_min:
        print(cps)
        break

        # if cookie_money > time_machine_price:
        #     time_machine.click()
        # elif cookie_money > portal_price:
        #     portal.click()
        # elif cookie_money > alchemy_price:
        #     alchemy.click()
        # elif cookie_money > shipment_price:
        #     shipment.click()
        # elif cookie_money > mine_price:
        #     mine.click()
        # elif cookie_money > factory_price:
        #     factory.click()
        # elif cookie_money > grandma_price:
        #     grandma.click()
        # elif cookie_money > cursor_price:
        #     cursor.click()
        # else:
        #     cookie.click()

##########################################################
##########################################################

# search1 = driver.find_element(By.NAME, "fName")
# search1.send_keys("Bruce")
# search4 = driver.find_element(By.CSS_SELECTOR, "form button")
# search4.click()

# time.sleep(5)


"""
Core visualization operations based on Mayavi.

Actual implementation of _Renderer and _Projection classes.
"""

# Authors: Alexandre Gramfort <alexandre.gramfort@inria.fr>
#          Denis Engemann <denis.engemann@gmail.com>
#          Martin Luessi <mluessi@nmr.mgh.harvard.edu>
#          Eric Larson <larson.eric.d@gmail.com>
#          Mainak Jas <mainak@neuro.hut.fi>
#          Mark Wronkiewicz <wronk.mark@gmail.com>
#          Joan Massich <mailsik@gmail.com>
#          Guillaume Favelier <guillaume.favelier@gmail.com>
#
# License: Simplified BSD

from contextlib import contextmanager
import warnings
import numpy as np

from mayavi.core.scene import Scene
from mayavi.core.ui.mayavi_scene import MayaviScene
from tvtk.pyface.tvtk_scene import TVTKScene

from .base_renderer import _BaseRenderer
from ._utils import _check_color
from ...surface import _normalize_vectors
from ...utils import (_import_mlab, _validate_type, SilenceStdout,
                      copy_base_doc_to_subclass_doc)

class _Projection(object):
    """Class storing projection information.

    Attributes
    ----------
    xy : array
        Result of 2d projection of 3d data.
    pts : Source
        Mayavi source handle.
    """

    def __init__(self, xy=None, pts=None):
        """Store input projection information into attributes."""
        self.xy = xy
        self.pts = pts

    def visible(self, state):
        """Modify visibility attribute of the source."""
        if self.pts is not None:
            self.pts.visible = state

@copy_base_doc_to_subclass_doc
class _Renderer(_BaseRenderer):
    """Class managing rendering scene.

    Attributes
    ----------
    mlab: mayavi.mlab
        Main Mayavi access point.
    fig: mlab.Figure
        Mayavi scene handle.
    """

    def __init__(self, fig=None, size=(600, 600), bgcolor='black',
                 name=None, show=False, shape=(1, 1)):
        if bgcolor is not None:
            bgcolor = _check_color(bgcolor)
        self.mlab = _import_mlab()
        self.shape = shape
        if fig is None:
            self.fig = _mlab_figure(figure=name, bgcolor=bgcolor, size=size)
        elif isinstance(fig, int):
            self.fig = _mlab_figure(figure=fig, bgcolor=bgcolor, size=size)
        else:
            self.fig = fig
        _toggle_mlab_render(self.fig, show)

    def subplot(self, x, y):
        pass

    def scene(self):
        return self.fig

    def set_interactive(self):
        from tvtk.api import tvtk
        if self.fig.scene is not None:
            self.fig.scene.interactor.interactor_style = \
                tvtk.InteractorStyleTerrain()

    def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
             backface_culling=False, scalars=None, colormap=None,
             vmin=None, vmax=None, **kwargs):
        if color is not None:
            color = _check_color(color)
        if color is not None and isinstance(color, np.ndarray) \
           and color.ndim > 1:
            if color.shape[1] == 3:
                vertex_color = np.c_[color, np.ones(len(color))] * 255.0
            else:
                vertex_color = color * 255.0
            # create a lookup table to enable one color per vertex
            scalars = np.arange(len(color))
            color = None
        else:
            vertex_color = None
        with warnings.catch_warnings(record=True):  # traits
            surface = self.mlab.triangular_mesh(x, y, z, triangles,
                                                color=color,
                                                scalars=scalars,
                                                opacity=opacity,
                                                figure=self.fig,
                                                vmin=vmin,
                                                vmax=vmax,
                                                **kwargs)
            if vertex_color is not None:
                surface.module_manager.scalar_lut_manager.lut.table = \
                    vertex_color
            elif isinstance(colormap, np.ndarray):
                l_m = surface.module_manager.scalar_lut_manager
                if colormap.dtype == np.uint8:
                    l_m.lut.table = colormap
                elif colormap.dtype == np.float:
                    l_m.load_lut_from_list(colormap)
                else:
                    raise TypeError('Expected type for colormap values are'
                                    ' np.float or np.uint8: '
                                    '{} was given'.format(colormap.dtype))
            surface.actor.property.shading = shading
            surface.actor.property.backface_culling = backface_culling
        return surface

    def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,
                vmin=None, vmax=None, colormap=None,
                normalized_colormap=False, kind='line', color=None):
        mesh = _create_mesh_surf(surface, self.fig, scalars=scalars)
        with warnings.catch_warnings(record=True):  # traits
            cont = self.mlab.pipeline.contour_surface(
                mesh, contours=contours, line_width=width, vmin=vmin,
                vmax=vmax, opacity=opacity, figure=self.fig)
            cont.module_manager.scalar_lut_manager.lut.table = colormap

    def surface(self, surface, color=None, opacity=1.0,
                vmin=None, vmax=None, colormap=None,
                normalized_colormap=False, scalars=None,
                backface_culling=False):
        if color is not None:
            color = _check_color(color)
        if normalized_colormap:
            colormap = colormap * 255.0
        # Make a solid surface
        mesh = _create_mesh_surf(surface, self.fig, scalars=scalars)
        with warnings.catch_warnings(record=True):  # traits
            surface = self.mlab.pipeline.surface(
                mesh, color=color, opacity=opacity, vmin=vmin, vmax=vmax,
                figure=self.fig)
            if colormap is not None:
                surface.module_manager.scalar_lut_manager.lut.table = colormap
            surface.actor.property.backface_culling = backface_culling

    def sphere(self, center, color, scale, opacity=1.0,
               resolution=8, backface_culling=False,
               radius=None):
        color = _check_color(color)
        center = np.atleast_2d(center)
        x, y, z = center.T
        surface = self.mlab.points3d(x, y, z, color=color,
                                     resolution=resolution,
                                     scale_factor=scale, opacity=opacity,
                                     figure=self.fig)
        surface.actor.property.backface_culling = backface_culling

    def tube(self, origin, destination, radius=0.001, color='white',
             scalars=None, vmin=None, vmax=None, colormap='RdBu',
             normalized_colormap=False, reverse_lut=False):
        color = _check_color(color)
        origin = np.atleast_2d(origin)
        destination = np.atleast_2d(destination)
        if scalars is None:
            surface = self.mlab.plot3d([origin[:, 0], destination[:, 0]],
                                       [origin[:, 1], destination[:, 1]],
                                       [origin[:, 2], destination[:, 2]],
                                       tube_radius=radius,
                                       color=color,
                                       figure=self.fig)
        else:
            surface = self.mlab.plot3d([origin[:, 0], destination[:, 0]],
                                       [origin[:, 1], destination[:, 1]],
                                       [origin[:, 2], destination[:, 2]],
                                       [scalars[:, 0], scalars[:, 1]],
                                       tube_radius=radius,
                                       vmin=vmin,
                                       vmax=vmax,
                                       colormap=colormap,
                                       figure=self.fig)
        surface.module_manager.scalar_lut_manager.reverse_lut = reverse_lut
        return surface

    def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8,
                 glyph_height=None, glyph_center=None, glyph_resolution=None,
                 opacity=1.0, scale_mode='none', scalars=None,
                 backface_culling=False, colormap=None, vmin=None, vmax=None,
                 line_width=2., name=None):
        color = _check_color(color)
        with warnings.catch_warnings(record=True):  # traits
            if mode in ('arrow', '2darrow', '3darrow'):
                self.mlab.quiver3d(x, y, z, u, v, w, mode=mode,
                                   color=color, scale_factor=scale,
                                   scale_mode=scale_mode,
                                   resolution=resolution, scalars=scalars,
                                   opacity=opacity, figure=self.fig)
            elif mode == 'cone':
                self.mlab.quiver3d(x, y, z, u, v, w, color=color,
                                   mode=mode, scale_factor=scale,
                                   opacity=opacity, figure=self.fig)
            elif mode == 'cylinder':
                quiv = self.mlab.quiver3d(x, y, z, u, v, w, mode=mode,
                                          color=color, scale_factor=scale,
                                          opacity=opacity, figure=self.fig)
                quiv.glyph.glyph_source.glyph_source.height = glyph_height
                quiv.glyph.glyph_source.glyph_source.center = glyph_center
                quiv.glyph.glyph_source.glyph_source.resolution = \
                    glyph_resolution
                quiv.actor.property.backface_culling = backface_culling

    def text2d(self, x_window, y_window, text, size=14, color='white',
               justification=None):
        if color is not None:
            color = _check_color(color)
        size = 14 if size is None else size
        with warnings.catch_warnings(record=True):  # traits
            text = self.mlab.text(x_window, y_window, text, color=color,
                                  figure=self.fig)
            text.property.font_size = size
            text.actor.text_scale_mode = 'viewport'
            if isinstance(justification, str):
                text.property.justification = justification

    def text3d(self, x, y, z, text, scale, color='white'):
        color = _check_color(color)
        with warnings.catch_warnings(record=True):  # traits
            self.mlab.text3d(x, y, z, text, scale=scale, color=color,
                             figure=self.fig)

    def scalarbar(self, source, title=None, n_labels=4, bgcolor=None):
        with warnings.catch_warnings(record=True):  # traits
            self.mlab.scalarbar(source, title=title, nb_labels=n_labels)
        if bgcolor is not None:
            from tvtk.api import tvtk
            bgcolor = np.asarray(bgcolor)
            bgcolor = np.append(bgcolor, 1.0) * 255.
            cmap = source.module_manager.scalar_lut_manager
            lut = cmap.lut
            ctable = lut.table.to_array()
            cbar_lut = tvtk.LookupTable()
            cbar_lut.deep_copy(lut)
            alphas = ctable[:, -1][:, np.newaxis] / 255.
            use_lut = ctable.copy()
            use_lut[:, -1] = 255.
            vals = (use_lut * alphas) + bgcolor * (1 - alphas)
            cbar_lut.table.from_array(vals)
            cmap.scalar_bar.lookup_table = cbar_lut

    def show(self):
        if self.fig is not None:
            _toggle_mlab_render(self.fig, True)

    def close(self):
        _close_3d_figure(figure=self.fig)

    def set_camera(self, azimuth=None, elevation=None, distance=None,
                   focalpoint=None):
        _set_3d_view(figure=self.fig, azimuth=azimuth,
                     elevation=elevation, distance=distance,
                     focalpoint=focalpoint)

    def reset_camera(self):
        renderer = getattr(self.fig.scene, 'renderer', None)
        if renderer is not None:
            renderer.reset_camera()

    def screenshot(self, mode='rgb', filename=None):
        return _take_3d_screenshot(figure=self.fig, mode=mode,
                                   filename=filename)

    def project(self, xyz, ch_names):
        xy = _3d_to_2d(self.fig, xyz)
        xy = dict(zip(ch_names, xy))
        pts = self.fig.children[-1]

        return _Projection(xy=xy, pts=pts)

    def enable_depth_peeling(self):
        if self.fig.scene is not None:
            self.fig.scene.renderer.use_depth_peeling = True

def _mlab_figure(**kwargs):
    """Create a Mayavi figure using our defaults."""
    fig = _import_mlab().figure(**kwargs)
    # If using modern VTK/Mayavi, improve rendering with FXAA
    if hasattr(getattr(fig.scene, 'renderer', None), 'use_fxaa'):
        fig.scene.renderer.use_fxaa = True
    return fig

def _toggle_mlab_render(fig, render):
    mlab = _import_mlab()
    if mlab.options.backend != 'test':
        fig.scene.disable_render = not render

def _create_mesh_surf(surf, fig=None, scalars=None, vtk_normals=True):
    """Create Mayavi mesh from MNE surf."""
    mlab = _import_mlab()
    x, y, z = surf['rr'].T
    with warnings.catch_warnings(record=True):  # traits
        mesh = mlab.pipeline.triangular_mesh_source(
            x, y, z, surf['tris'], scalars=scalars, figure=fig)
    if vtk_normals:
        mesh = mlab.pipeline.poly_data_normals(mesh)
        mesh.filter.compute_cell_normals = False
        mesh.filter.consistency = False
        mesh.filter.non_manifold_traversal = False
        mesh.filter.splitting = False
    else:
        # make absolutely sure these are normalized for Mayavi
        nn = surf['nn'].copy()
        _normalize_vectors(nn)
        mesh.data.point_data.normals = nn
        mesh.data.cell_data.normals = None
    return mesh

def _3d_to_2d(fig, xyz):
    """Convert 3d points to a 2d perspective using a Mayavi Scene."""
    _validate_type(fig, Scene, "fig", "Scene")
    xyz = np.column_stack([xyz, np.ones(xyz.shape[0])])

    # Transform points into 'unnormalized' view coordinates
    comb_trans_mat = _get_world_to_view_matrix(fig.scene)
    view_coords = np.dot(comb_trans_mat, xyz.T).T

    # Divide through by the fourth element for normalized view coords
    norm_view_coords = view_coords / (view_coords[:, 3].reshape(-1, 1))

    # Transform from normalized view coordinates to display coordinates.
    view_to_disp_mat = _get_view_to_display_matrix(fig.scene)
    xy = np.dot(view_to_disp_mat, norm_view_coords.T).T

    # Pull the first two columns since they're meaningful for 2d plotting
    xy = xy[:, :2]
    return xy

def _get_world_to_view_matrix(scene):
    """Return the 4x4 matrix to transform xyz space to the current view.

    This is a concatenation of the model view and perspective transforms.
    """
    _validate_type(scene, (MayaviScene, TVTKScene), "scene",
                   "TVTKScene/MayaviScene")
    cam = scene.camera

    # The VTK method needs the aspect ratio and near and far
    # clipping planes in order to return the proper transform.
    scene_size = tuple(scene.get_size())
    clip_range = cam.clipping_range
    aspect_ratio = float(scene_size[0]) / scene_size[1]

    # Get the vtk matrix object using the aspect ratio we defined
    vtk_comb_trans_mat = cam.get_composite_projection_transform_matrix(
        aspect_ratio, clip_range[0], clip_range[1])
    vtk_comb_trans_mat = vtk_comb_trans_mat.to_array()
    return vtk_comb_trans_mat

def _get_view_to_display_matrix(scene):
    """Return the 4x4 matrix to convert view coordinates to display coordinates.

    It's assumed that the view should take up the entire window and that the
    origin of the window is in the upper left corner.
    """  # noqa: E501
    _validate_type(scene, (MayaviScene, TVTKScene), "scene",
                   "TVTKScene/MayaviScene")

    # normalized view coordinates have the origin in the middle of the space
    # so we need to scale by width and height of the display window and shift
    # by half width and half height. The matrix accomplishes that.
    x, y = tuple(scene.get_size())
    view_to_disp_mat = np.array([[x / 2.0,       0.,   0.,   x / 2.0],  # noqa: E241,E501
                                 [0.,      -y / 2.0,   0.,   y / 2.0],  # noqa: E241,E501
                                 [0.,            0.,   1.,        0.],  # noqa: E241,E501
                                 [0.,            0.,   0.,        1.]])  # noqa: E241,E501
    return view_to_disp_mat

def _close_all():
    from mayavi import mlab
    mlab.close(all=True)

def _set_3d_view(figure, azimuth, elevation, focalpoint, distance):
    from mayavi import mlab
    with warnings.catch_warnings(record=True):  # traits
        with SilenceStdout():
            mlab.view(azimuth, elevation, distance,
                      focalpoint=focalpoint, figure=figure)
            mlab.draw(figure)

def _set_3d_title(figure, title, size=40):
    from mayavi import mlab
    text = mlab.title(text='', figure=figure)
    text.property.vertical_justification = 'top'
    text.property.font_size = size
    mlab.draw(figure)

def _check_3d_figure(figure):
    try:
        import mayavi  # noqa F401
    except Exception:
        raise TypeError('figure must be a mayavi scene but the'
                        'mayavi package is not found.')
    else:
        from mayavi.core.scene import Scene
        if not isinstance(figure, Scene):
            raise TypeError('figure must be a mayavi scene.')

def _save_figure(img, filename):
    from matplotlib.backends.backend_agg import FigureCanvasAgg
    from matplotlib.figure import Figure
    fig = Figure(frameon=False)
    FigureCanvasAgg(fig)
    fig.figimage(img, resize=True)
    fig.savefig(filename)

def _close_3d_figure(figure):
    from mayavi import mlab
    mlab.close(figure)

def _take_3d_screenshot(figure, mode='rgb', filename=None):
    from mayavi import mlab
    from mne.viz.backends.renderer import MNE_3D_BACKEND_TESTING
    if MNE_3D_BACKEND_TESTING:
        ndim = 3 if mode == 'rgb' else 4
        if figure.scene is None:
            figure_size = (600, 600)
        else:
            figure_size = figure.scene._renwin.size
        return np.zeros(tuple(figure_size) + (ndim,), np.uint8)
    else:
        from pyface.api import GUI
        gui = GUI()
        gui.process_events()
        with warnings.catch_warnings(record=True):  # traits
            img = mlab.screenshot(figure, mode=mode)
        if isinstance(filename, str):
            _save_figure(img, filename)
        return img

@contextmanager
def _testing_context(interactive):
    mlab = _import_mlab()
    orig_backend = mlab.options.backend
    mlab.options.backend = 'test'
    try:
        yield
    finally:
        mlab.options.backend = orig_backend


import matplotlib.pyplot as plt
from rightUp import move_right_slant
from moveRight import move_right

def polyline_detection(incision, incision_original, image):
    x_len = len(incision)
    y_len = len(incision[1])

    incision_points = list()  # for the final detection

    y_position = 0  # on which column is the pixel true
    x_position = 0
    possibilities = list()  # if there is another possible way in the net
    tolerance = 6  # the tolerance band
    incision_continue = True
    steps_up = 3  # how many steps am I able to make UP
    steps_down = 3  # how many steps am I able to make DOWN
    up_counter = 0
    down_counter = 0
    up_down = False
    up_down_steps = 0
    x_diff_right_up = 0
    y_diff_right_up = 0
    x_diff_right_down = 0
    y_diff_right_down = 0

    for y in range(y_position, y_len):
        if incision_continue:
            for x in range(x_position, x_len):
                # print(str(x) + ", " + str(y))
                # direction: right
                if incision[x][y]:
                    outs = move_right(x, y, tolerance, incision_points, possibilities, x_len, incision,
                                      incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break

                # direction: right up
                elif incision[x-1][y]:
                    outs = move_right_slant(x, y, 0, tolerance, incision_points, possibilities, "up", x_len, incision,
                                            incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break

                # direction: right up - higher tolerance band
                elif incision[x-2][y]:
                    outs = move_right_slant(x, y, 1, tolerance, incision_points, possibilities, "up", x_len, incision,
                                            incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break

                # direction: right down
                elif x+1 != x_len and incision[x+1][y]:
                    outs = move_right_slant(x, y, 0, tolerance, incision_points, possibilities, "down", x_len, incision,
                                            incision_continue, image)
                    x_position = outs[0]
                    incision_points = outs[1]
                    possibilities = outs[2]
                    incision_continue = outs[3]
                    break
        else:
            break

    x_values = list()
    y_values = list()
    for i in range(0, len(incision_points)):
        x_values.append(incision_points[i][0])
        y_values.append(incision_points[i][1])
    plt.subplot(211)
    plt.plot(y_values, x_values)
    plt.xlim([0, y_len])
    plt.ylim([x_len, 0])
    plt.subplot(212)
    plt.imshow(incision_original, cmap="gray")
    plt.show()

    return incision_points


from __future__ import print_function
import os
import argparse
import datetime
import six
import math

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
from torchvision import datasets
from torchvision import transforms
from torch.autograd import Variable
from tqdm import tqdm

import numpy as np

from tensorboardX import SummaryWriter
import callbacks

# Training settings
parser = argparse.ArgumentParser(description='Deep Learning JHU Assignment 1 - Fashion-MNIST')
parser.add_argument('--batch-size', type=int, default=256, metavar='B',
                    help='input batch size for training (default: 64)')
parser.add_argument('--test-batch-size', type=int, default=1000, metavar='TB',
                    help='input batch size for testing (default: 1000)')
parser.add_argument('--epochs', type=int, default=10, metavar='E',
                    help='number of epochs to train (default: 10)')
parser.add_argument('--lr', type=float, default=0.01, metavar='LR',
                    help='learning rate (default: 0.01)')
parser.add_argument('--optimizer', type=str, default='sgd', metavar='O',
                    help='Optimizer options are sgd, p1sgd, adam, rms_prop')
parser.add_argument('--momentum', type=float, default=0.5, metavar='MO',
                    help='SGD momentum (default: 0.5)')
parser.add_argument('--no-cuda', action='store_true', default=False,
                    help='disables CUDA training')
parser.add_argument('--seed', type=int, default=1, metavar='S',
                    help='random seed (default: 1)')
parser.add_argument('--log_interval', type=int, default=100, metavar='I',
                    help="""how many batches to wait before logging detailed
                            training status, 0 means never log """)
parser.add_argument('--dataset', type=str, default='mnist', metavar='D',
                    help='Options are mnist and fashion_mnist')
parser.add_argument('--data_dir', type=str, default='../data/', metavar='F',
                    help='Where to put data')
parser.add_argument('--name', type=str, default='', metavar='N',
                    help="""A name for this training run, this
                            affects the directory so use underscores and not spaces.""")
parser.add_argument('--model', type=str, default='default', metavar='M',
                    help="""Options are default, P2Q7DefaultChannelsNet,
                    P2Q7HalfChannelsNet, P2Q7DoubleChannelsNet,
                    P2Q8BatchNormNet, P2Q9DropoutNet, P2Q10DropoutBatchnormNet,
                    P2Q11ExtraConvNet, P2Q12RemoveLayerNet, and P2Q13UltimateNet.""")
parser.add_argument('--print_log', action='store_true', default=False,
                    help='prints the csv log when training is complete')

required = object()

def timeStamped(fname, fmt='%Y-%m-%d-%H-%M-%S_{fname}'):
    """ Add a timestamp to your training run's name.
    """
    # http://stackoverflow.com/a/5215012/99379
    return datetime.datetime.now().strftime(fmt).format(fname=fname)

# choose the dataset

def prepareDatasetAndLogging(args):
    # choose the dataset
    if args.dataset == 'mnist':
        DatasetClass = datasets.MNIST
    elif args.dataset == 'fashion_mnist':
        DatasetClass = datasets.FashionMNIST
    else:
        raise ValueError('unknown dataset: ' + args.dataset + ' try mnist or fashion_mnist')

    training_run_name = timeStamped(args.dataset + '_' + args.name)

    kwargs = {'num_workers': 1, 'pin_memory': True} if args.cuda else {}

    # Create the dataset, mnist or fasion_mnist
    dataset_dir = os.path.join(args.data_dir, args.dataset)
    training_run_dir = os.path.join(args.data_dir, training_run_name)
    train_dataset = DatasetClass(
        dataset_dir, train=True, download=True,
        transform=transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize((0.1307,), (0.3081,))
        ]))
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=args.batch_size, shuffle=True, **kwargs)
    test_dataset = DatasetClass(
        dataset_dir, train=False, transform=transforms.Compose([
                        transforms.ToTensor(),
                        transforms.Normalize((0.1307,), (0.3081,))
                    ]))
    test_loader = torch.utils.data.DataLoader(
        test_dataset, batch_size=args.test_batch_size, shuffle=True, **kwargs)

    # Set up visualization and progress status update code
    callback_params = {'epochs': args.epochs,
                       'samples': len(train_loader) * args.batch_size,
                       'steps': len(train_loader),
                       'metrics': {'acc': np.array([]),
                                   'loss': np.array([]),
                                   'val_acc': np.array([]),
                                   'val_loss': np.array([])}}
    if args.print_log:
        output_on_train_end = os.sys.stdout
    else:
        output_on_train_end = None

    callbacklist = callbacks.CallbackList(
        [callbacks.BaseLogger(),
         callbacks.TQDMCallback(),
         callbacks.CSVLogger(filename=training_run_dir + training_run_name + '.csv',
                             output_on_train_end=output_on_train_end)])
    callbacklist.set_params(callback_params)

    tensorboard_writer = SummaryWriter(log_dir=training_run_dir, comment=args.dataset + '_embedding_training')

    # show some image examples in tensorboard projector with inverted color
    images = 255 - test_dataset.test_data[:100].float()
    label = test_dataset.test_labels[:100]
    features = images.view(100, 784)
    tensorboard_writer.add_embedding(features, metadata=label, label_img=images.unsqueeze(1))
    return tensorboard_writer, callbacklist, train_loader, test_loader

# TODO Add classes for every option listed under the --model parser argument above.

# Define the neural network classes

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        # F is just a functional wrapper for modules from the nn package
        # see http://pytorch.org/docs/_modules/torch/nn/functional.html
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2(x), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

class P2Q7HalfChannelsNet(nn.Module):
    def __init__(self):
        super(P2Q7HalfChannelsNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q7DoubleChannelsNet(nn.Module):
    def __init__(self):
        super(P2Q7DoubleChannelsNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q8BatchNormNet(nn.Module):
    def __init__(self):
        super(P2Q8BatchNormNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q9DropoutNet(nn.Module):
    def __init__(self):
        super(P2Q9DropoutNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q10DropoutBatchnormNet(nn.Module):
    def __init__(self):
        super(P2Q10DropoutBatchnormNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q11ExtraConvNet(nn.Module):
    def __init__(self):
        super(P2Q11ExtraConvNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q12RemoveLayerNet(nn.Module):
    def __init__(self):
        super(P2Q12RemoveLayerNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

class P2Q13UltimateNet(nn.Module):
    def __init__(self):
        super(P2Q13UltimateNet, self).__init__()
        # TODO Implement me
        raise NotImplementedError

    def forward(self, x):
        # TODO Implement me
        raise NotImplementedError

def chooseModel(model_name='default', cuda=True):
    # TODO add all the other models here if their parameter is specified
    if model_name == 'default' or model_name == 'P2Q7DefaultChannelsNet':
        model = Net()
    elif model_name in globals():
        model = globals()[model_name]()
    else:
        raise ValueError('Unknown model type: ' + model_name)

    if args.cuda:
        model.cuda()
    return model

def chooseOptimizer(model, optimizer='sgd'):
    if optimizer == 'sgd':
        optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)
    elif optimizer == 'adam':
        optimizer = optim.Adam(model.parameters())
    elif optimizer == 'rmsprop':
        optimizer = optim.RMSprop(model.parameters())
    else:
        raise ValueError('Unsupported optimizer: ' + args.optimizer)
    return optimizer

def train(model, optimizer, train_loader, tensorboard_writer, callbacklist, epoch, total_minibatch_count):
    # Training
    model.train()
    correct_count = np.array(0)
    for batch_idx, (data, target) in enumerate(train_loader):
        callbacklist.on_batch_begin(batch_idx)
        if args.cuda:
            data, target = data.cuda(), target.cuda()
        data, target = Variable(data), Variable(target)
        optimizer.zero_grad()

        # Forward prediction step
        output = model(data)
        loss = F.nll_loss(output, target)

        # Backpropagation step
        loss.backward()
        optimizer.step()

        # The batch has ended, determine the
        # accuracy of the predicted outputs
        _, argmax = torch.max(output, 1)

        # target labels and predictions are
        # categorical values from 0 to 9.
        accuracy = (target == argmax.squeeze()).float().mean()
        pred = output.data.max(1, keepdim=True)[1]  # get the index of the max log-probability
        correct_count += pred.eq(target.data.view_as(pred)).cpu().sum()

        batch_logs = {
            'loss': np.array(loss.data[0]),
            'acc': np.array(accuracy.data[0]),
            'size': np.array(len(target))
        }

        batch_logs['batch'] = np.array(batch_idx)
        callbacklist.on_batch_end(batch_idx, batch_logs)

        if args.log_interval != 0 and total_minibatch_count % args.log_interval == 0:
            # put all the logs in tensorboard
            for name, value in six.iteritems(batch_logs):
                tensorboard_writer.add_scalar(name, value, global_step=total_minibatch_count)

            # put all the parameters in tensorboard histograms
            for name, param in model.named_parameters():
                name = name.replace('.', '/')
                tensorboard_writer.add_histogram(name, param.data.cpu().numpy(), global_step=total_minibatch_count)
                tensorboard_writer.add_histogram(name + '/gradient', param.grad.data.cpu().numpy(), global_step=total_minibatch_count)

        total_minibatch_count += 1

    # display the last batch of images in tensorboard
    img = torchvision.utils.make_grid(255 - data.data, normalize=True, scale_each=True)
    tensorboard_writer.add_image('images', img, global_step=total_minibatch_count)

    return total_minibatch_count

def test(model, test_loader, tensorboard_writer, callbacklist, epoch, total_minibatch_count):
    # Validation Testing
    model.eval()
    test_loss = 0
    correct = 0
    progress_bar = tqdm(test_loader, desc='Validation')
    for data, target in progress_bar:
        if args.cuda:
            data, target = data.cuda(), target.cuda()
        data, target = Variable(data, volatile=True), Variable(target)
        output = model(data)
        test_loss += F.nll_loss(output, target, size_average=False).data[0]  # sum up batch loss
        pred = output.data.max(1, keepdim=True)[1]  # get the index of the max log-probability
        correct += pred.eq(target.data.view_as(pred)).cpu().sum()

    test_size = np.array(len(test_loader.dataset), np.float32)
    test_loss /= test_size

    acc = np.array(correct, np.float32) / test_size
    epoch_logs = {'val_loss': np.array(test_loss),
                  'val_acc': np.array(acc)}
    for name, value in six.iteritems(epoch_logs):
        tensorboard_writer.add_scalar(name, value, global_step=total_minibatch_count)
    callbacklist.on_epoch_end(epoch, epoch_logs)
    progress_bar.write(
        'Epoch: {} - validation test results - Average val_loss: {:.4f}, val_acc: {}/{} ({:.2f}%)'.format(
            epoch, test_loss, correct, len(test_loader.dataset),
            100. * correct / len(test_loader.dataset)))

    return acc

def run_experiment(args):
    total_minibatch_count = 0
    args.cuda = not args.no_cuda and torch.cuda.is_available()

    torch.manual_seed(args.seed)
    if args.cuda:
        torch.cuda.manual_seed(args.seed)

    epochs_to_run = args.epochs
    tensorboard_writer, callbacklist, train_loader, test_loader = prepareDatasetAndLogging(args)
    model = chooseModel(args.model)
    # tensorboard_writer.add_graph(model, images[:2])
    optimizer = chooseOptimizer(model, args.optimizer)
    # Run the primary training loop, starting with validation accuracy of 0
    val_acc = 0
    callbacklist.on_train_begin()
    for epoch in range(1, epochs_to_run + 1):
        callbacklist.on_epoch_begin(epoch)
        # train for 1 epoch
        total_minibatch_count = train(model, optimizer, train_loader, tensorboard_writer,
                                      callbacklist, epoch, total_minibatch_count)
        # validate progress on test dataset
        val_acc = test(model, test_loader, tensorboard_writer,
                       callbacklist, epoch, total_minibatch_count)
    callbacklist.on_train_end()
    tensorboard_writer.close()

    if args.dataset == 'fashion_mnist' and val_acc > 0.92 and val_acc <= 1.0:
        print("Congratulations, you beat the Question 13 minimum of 92 with ({:.2f}%) validation accuracy!".format(val_acc))

if __name__ == '__main__':
    args = parser.parse_args()
    # Run the primary training and validation loop
    run_experiment(args)


import sys
input = lambda: sys.stdin.readline().rstrip()
from collections import deque

def resolve():
    R, C = map(int, input().split())
    sy, sx = map(int, input().split())
    gy, gx = map(int, input().split())
    c = [list(input().split()) for _ in range(R)]

    dir = ((0, 1), (1, 0), (0, -1), (-1, 0))
    dist = [[float('inf')]*C for _ in range(R)]

    que = deque()
    que.append(((sy-1, sx-1), 0, set()))
    while len(que)>0:
        now = que.popleft()
        dist[now[0][0]][now[0][1]] = min(now[1], dist[now[0][0]][now[0][1]])
        for d in dir:
            next = (now[0][0]+d[0], now[0][1]+d[1])
            print(next)
            if 0<=next[0]<R and 0<=next[1]<C and c[next[0]][next[1]]=='.' and not next in now[2]:
                que.append((next, now[1]+1, now[2]|{now[0]}))

    print(dist[gy-1][gx-1])

if __name__ == '__main__':
    resolve()


import sys, re
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
from PyQt5 import uic

class Dialog(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        uic.loadUi("poll.ui", self)
        self.name.textChanged.connect(self.validarNombre)
        self.email.textChanged.connect(self.validarEmail)
        self.send.clicked.connect(self.validarFormulario)

    def validarNombre(self):
        nombre = self.name.text()
        validar = re.match('^[a-z\sáéíóúàèìòùäëïöüñ]+$', nombre, re.I)
        if nombre == "":
            self.name.setStyleSheet("border: 1px solid yellow")
            return False
        elif not validar:
            self.name.setStyleSheet("border: 1px solid red")
            return False
        else:
            self.name.setStyleSheet("border: 1px solid green")
            return True

    def validarEmail(self):
        email = self.email.text()
        validar = re.match('^[a-zA-Z0-9\._-]+@[a-zA-Z0-9-]{2,}[.][a-zA-Z]{2,4}$', email, re.I)
        if email == "":
            self.email.setStyleSheet("border: 1px solid yellow")
            return False
        elif not validar:
            self.email.setStyleSheet("border: 1px solid red")
            return False
        else:
            self.email.setStyleSheet("border: 1px solid green")
            return True

    def validarFormulario(self):
        if self.validarNombre() and self.validarEmail():
            QMessageBox.information(self, "Formulario correcto", "validación correcta", QMessageBox.Discard)
        else:
            QMessageBox.warning(self, "Formulario incorrecto", "Fallo en la validación", QMessageBox.Discard)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    dialogo = Dialog()
    dialogo.show()
    app.exec_()