import datetime
import traceback

# this is modified code from an older private project

class Lesson:
    def __init__(
        self,
        name: str,
        teacher: str,
        ausfall: bool,
        student_id: int,
        student_data: dict,
    ):
        self.data = {}
        self.deletion_candiate = False
        if name == "":
            self.apply_for_deletion()
        elif name in ["Bio", "Chemie", "Informatik"]:
            if student_data[name] is True:
                pass
            else:
                self.apply_for_deletion()
                return
        elif name == "Kreatives Fach":
            name = student_data["Kreatives Fach"]
            teacher = student_data["kLehrer"]
        elif name == "Reli / Philo":
            name = student_data["Reli / Philo"]
            teacher = student_data["rLehrer"]

        self.name = name
        self.teacher = teacher
        self.ausfall = ausfall
        self.student_id = student_id
        self.data = {
            "name": name,
            "teacher": teacher,
            "ausfall": ausfall,
            "student_id": student_id,
        }

    def apply_for_deletion(self):
        self.deletion_candiate = True
        self.data.update({"name": " ", "teacher": " ", "ausfall": True})
        self.name = " "
        self.teacher = " "
        self.ausfall = True

class Day:
    def __init__(
        self,
        fortnight_day: int,
        even_week: bool,
        day_data: list[list[str, str, float]],
        student_id: int,
        student_data: dict,
    ):
        self.lessons = []
        self.fortnight_day = fortnight_day
        self.even_week = even_week
        self.student_id = student_id
        for lesson_data in day_data:
            self.lessons.append(
                Lesson(
                    lesson_data[0],
                    lesson_data[1],
                    lesson_data[2],
                    student_id,
                    student_data,
                )
            )
        self.cleanup()

    def actual_lessons(self):
        lessons = 0
        for lesson in self.lessons:
            if lesson.ausfall == False:
                lessons += 1
        return lessons

    def showself(self, today=True):
        self.cleanup()
        return_string = ""
        cancelled = 0
        for lesson in self.lessons:
            cancelled += 1 if lesson.ausfall == True else 0
        if len(self.lessons) == 0 or cancelled == len(self.lessons):
            if today == True:
                return_string += "Heute ist schulfrei, schon vergessen?"
            else:
                return_string += "Morgen ist schulfrei, schon vergessen?"
        else:
            lessons = self.actual_lessons()
            if today == True:
                date = datetime.datetime.today().strftime("%d.%m.%Y")
                return_string += (
                    f"Heute, am {date}, hat <@{self.student_id}> {lessons} Blöcke.\n"
                )
                return_string += "------------\n"
                for lesson in self.lessons:
                    return_string += "**" + lesson.name + "**\n"
                    return_string += lesson.teacher + "\n"
                    return_string += "------------\n"
                    if self.lessons.index(lesson) == 2 and len(self.lessons) > 3:
                        return_string += "------------\n"
            else:
                datetime_tomorrow = datetime.datetime.today() + datetime.timedelta(
                    days=1
                )
                date = datetime_tomorrow.strftime("%d.%m.%Y")
                return_string += (
                    f"Am {date} hat <@{self.student_id}> {lessons} Blöcke.\n"
                )
                return_string += "------------\n"
                for lesson in self.lessons:
                    return_string += "**" + lesson.name + "**\n"
                    return_string += lesson.teacher + "\n"
                    return_string += "------------\n"
                    if self.lessons.index(lesson) == 2 and len(self.lessons) > 3:
                        return_string += "------------\n"
        return return_string

    def get_lesson(self, lesson, data):
        try:
            i = self.lessons[lesson]
        except IndexError:
            return " "
        else:
            return self.lessons[lesson].data[data]

    def cleanup(self):
        for lesson in self.lessons[::-1]:
            if lesson == self.lessons[-1] and lesson.deletion_candiate == True:
                self.lessons.remove(lesson)

class Stundenplan:
    def __init__(self, member_id, data):
        self.member_id = member_id
        self.days = []
        self.profil = data[member_id]["Profil"]
        self.data = data[self.profil]
        for day_data in self.data:
            even_week = False if self.data.index(day_data) > 6 else True
            self.days.append(
                Day(
                    self.data.index(day_data),
                    even_week,
                    day_data,
                    member_id,
                    data[member_id],
                )
            )

    def heute(self):
        weekday = datetime.datetime.today().weekday()
        if int(datetime.datetime.today().strftime("%V")) % 2 == 0:
            fortnight_day = weekday + 7
        else:
            fortnight_day = weekday
        if weekday > 13:
            weekday -= 14
        return self.days[fortnight_day].showself(today=True)

    def morgen(self):
        weekday_tomorrow = datetime.datetime.today().weekday() + 1
        if int(datetime.datetime.today().strftime("%V")) % 2 == 0:
            fortnight_day = weekday_tomorrow + 7
        else:
            fortnight_day = weekday_tomorrow
        if fortnight_day > 13:
            fortnight_day -= 14
        try:
            return self.days[fortnight_day].showself(today=False)
        except IndexError:
            print(traceback.format_exc())
            return f"Fehler! {fortnight_day} < 13. Pingt mich solange ich den befehl nicht selber eingegeben habe:\n ```{traceback.format_exc()}```"

    def row_splitter(self):
        return (
            "+"
            + 12 * "-"
            + "+"
            + 12 * "-"
            + "+"
            + 12 * "-"
            + "+"
            + 12 * "-"
            + "+"
            + 12 * "-"
            + "+"
            + "\n"
        )
        # +------------+------------+------------+------------+------------+\n

    def show_week(self):
        return_string = ""
        weekdays = [
            "Montag",
            "Dienstag",
            "Mittwoch",
            "Donnerstag",
            "Freitag",
            "Samstag",
            "Sonntag",
        ]
        return_string += f"Heute ist {weekdays[datetime.datetime.today().weekday()]}. "
        even_week = (
            True if int(datetime.datetime.today().strftime("%V")) % 2 == 0 else False
        )
        if datetime.datetime.today().weekday() > 4:
            return_string += f"Hier ist der Stundenplan von <@{self.member_id}> für **nächste** Woche:"
            even_week = not even_week
        else:
            return_string += (
                f"Hier ist der Stundenplan von <@{self.member_id}> für **diese** Woche:"
            )
        return_string += "```\n"
        return_string += self.row_splitter()
        day_numbers = [0, 1, 2, 3, 4] if even_week == False else [7, 8, 9, 10, 11]
        for lesson in [0, 1, 2, 3, 4]:
            if lesson == 3:
                return_string += self.row_splitter()
            for data in ["name", "teacher"]:
                return_string += "|"
                for day in day_numbers:
                    return_string += " "
                    lesson_data = self.days[day].get_lesson(lesson, data)
                    return_string += lesson_data
                    return_string += (11 - len(lesson_data)) * " "
                    return_string += "|"
                return_string += "\n"
            return_string += self.row_splitter()
        return_string += "```"
        return return_string

mats_data = {
    399183833901170689: {
        "Profil": "Physik",
        "Reli / Philo": "Philo",
        "rLehrer": "Priebe",
        "Kreatives Fach": "Musik",
        "kLehrer": "Wellner",
        "Chemie": False,
        "Bio": False,
        "Informatik": True,
    },
    "Physik": [
        [  # mon
            ["Latein", "Derlien", False],
            ["Physik", "Büsser", False],
            ["Deutsch", "Balasus", False],
            ["Bio", "Hudzik", False],
        ],
        [  # tue
            ["Englisch", "Bach", False],
            ["Latein", "Derlien", False],
            ["WiPo", "Becker", False],
            ["Physik", "Büsser", False],
            ["Deutsch", "Balasus", False],
        ],
        [  # wed
            ["Mathe", "Stamm", False],
            ["Chemie", "Weber", False],
            ["Englisch", "Bach", False],
        ],
        [  # thur
            ["Reli / Philo", "rLehrer", False],
            ["Erdkunde", "Boysen", False],
            ["Kreatives Fach", "kLehrer", False],
            ["Informatik", "Stieglitz?", False],
            ["Geschichte", "Heberlein", False],
        ],
        [
            ["", "", True],
            ["Sport", "Pilarczyk", False],
            ["Mathe", "Stamm", False],
        ],  # fri
        [],  # sat
        [],  # sun
        [  # mon
            ["Latein", "Derlien", False],
            ["Physik", "Büsser", False],
            ["Deutsch", "Balasus", False],
            ["Bio", "Hudzik", False],
        ],
        [  # tue
            ["Bio", "Hudzik", True],
            ["BerOri", "Bach", False],
            ["WiPo", "Becker", False],
            ["Physik", "Büsser", False],
        ],
        [  # wed
            ["Mathe", "Stamm", False],
            ["Chemie", "Weber", False],
            ["Englisch", "Bach", False],
            ["Informatik", "Stieglitz?", False],
        ],
        [  # thur
            ["Reli / Philo", "rLehrer", False],
            ["Erdkunde", "Boysen", False],
            ["Kreatives Fach", "kLehrer", False],
            ["Informatik", "Stieglitz?", False],
            ["Geschichte", "Heberlein", False],
        ],
        [
            ["", "", True],
            ["Sport", "Pilarczyk", False],
            ["WiPo", "Becker", False],
        ],  # fri
        [],  # sat
        [],  # sun
    ],
}
mats_stundenplan = Stundenplan(399183833901170689, mats_data)


#!/usr/bin/python
# -*- coding: UTF-8 -*-
'''
@author RenMeng
@since 2022/3/22
'''

class CQueue:

    def __init__(self):
        self.getin = []
        self.getout = []

    def appendTail(self, value: int) -> None:
        self.getin.append(value)

    def deleteHead(self) -> int:
        if len(self.getout) == 0:
            if len(self.getin) == 0:
                return -1
            while len(self.getin) > 0:
                ele = self.getin.pop(-1)
                self.getout.append(ele)
            return self.getout.pop(-1)
        return self.getout.pop(-1)


#!/usr/bin/python3
# -*- coding: utf-8 -*-

#ui python files
from qyh_stock import Ui_MainWindow
from edit import EditWindow

#function python files
from search_stock_name import get_stock_num_by_name
from get_dfcfgupiao import get_gupiao_info
from basic_function import *

#system python files
import sys, time, threading
import urllib.request
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
import os, re
import signal

notify_method = "mail"

class MyEditWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = EditWindow()
        self.ui.setupUi(self)
    def open(self):
        self.ui.open_file_all()
        self.show()
    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Escape:
            self.close()
        if e.key() == QtCore.Qt.Key_C:
            if e.modifiers() & QtCore.Qt.ControlModifier:
                print("ctrl + c press")
                self.close()
class MyWidgets(QWidget):
    msec = 1000
    pre_date_day = 0;
    running_cnt = 0
    stock_notify_already = {}
    debug = False
    update_running = 0
    def __init__(self):
        super().__init__()
        self.ui=Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.restart_button.clicked.connect(self.close)

        self.init_stock_info_from_file()
        try:
           t = threading.Thread(target=self.update_info_to_ui)
           t.start()
        except:
            print("init start fail")
        #  try:
           #  t = threading.Thread(target=self.update_info_to_ui)
           #  t.start()
        #  except:
            #  print("init 2 start fail")
        self.start()
    def closeEvent(self, event):
        event.accept()
        print("kill the programme")
        os.system('killall -9 qyh_stock_main_for_dfcf.py')
    def update_info_to_ui(self):
        thread_list = []
        t = threading.Thread(target=self.polling_get_zs_info)
        #  t.start()
        thread_list.append(t)
        t = threading.Thread(target=self.polling_get_stock_info)
        #  t.start()
        thread_list.append(t)
        t = threading.Thread(target=self.get_compare_info)
        #  t.start()
        thread_list.append(t)
        for t in thread_list:
            t.start()
        for t in thread_list:
            t.join()

    def get_zs_name(self, zs_str):
        if zs_str == "000001":
            return str("上证指数")
        elif zs_str == "399001":
            return str("深证指数")
        elif zs_str == "399006":
            return str("创业板")
        else:
            return str("未知指数")
    def get_stock_code_by_name(self, name):
        if name == "上证指数":
            return str("000001")
        elif name == "深证指数":
            return str("399001")
        elif name == "创业板":
            return str("399006")
        else:
            return str("未知指数")
    def edit(self):
        print("not implement, please wait")

    def close(self):
        os.system('(sleep 2;./qyh_stock_main_for_dfcf.py)&')
        os.system('killall -9 qyh_stock_main_for_dfcf.py')
    def polling_get_zs_info(self):
        cnt = 0
        zs_info_list = []
        with open("zs_pool.txt", "r") as f:
            for number, line in enumerate(f,start=1):
                line = line.strip('\n').strip('\r')
                if line != None:
                    line = re.sub(' +', ' ', line)
                    line_info = line.split(" ")
                name = line_info[0]
                name = self.get_stock_code_by_name(name)
                return_value = get_gupiao_info(name)
                if return_value != None:
                    (prePrice, price, time) = return_value
                    prePrice = float(prePrice)
                    price = float(price)
                    if price - prePrice > 0:
                        up_down_percent = str((price -prePrice) / prePrice * 100 + 0.005)[0:4]
                    else:
                        up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5]

                    up_down_value = str(price - prePrice)
                    value = str(price)
                    up_down_percent_result = up_down_percent + '%'
                    zs_stock_one_element_info = {}

                    #  zs_stock_one_element_info["stock_name"] = self.get_zs_name(name)
                    zs_stock_one_element_info["stock_name"] = line_info[0]
                    zs_stock_one_element_info["value"] = value
                    zs_stock_one_element_info["up_down_percent"] = up_down_percent_result
                    float_up_down = float(up_down_percent)
                    notify = self.ui.get_zs_notify_value(zs_stock_one_element_info["stock_name"])
                    zs_stock_one_element_info["notify"] = notify
                    #print("notify ", zs_stock_one_element_info["stock_name"], notify)
                    limit = str(self.ui.get_zs_limit_value(zs_stock_one_element_info["stock_name"]))
                    cnt = cnt + 1
                    if notify == True:
                        try:
                            limit = float(limit)
                            if (abs(float_up_down) > abs(limit)) and (notify == true) and (limit != 0):
                                zs_stock_one_element_info["color"] = "red"
                                notify_user_message(zs_stock_one_element_info["stock_name"], float_up_down, limit, notify_method)
                        except:
                            pass
                    zs_info_list.append(zs_stock_one_element_info)
        self.ui.update_zs_stock_info(zs_info_list)
    def init_stock_info_from_file(self):
        stock_list = []
        with open("stock_pool.txt", "r") as f:
            for number, line in enumerate(f,start=1):
                line = line.strip('\n').strip('\r')
                if line != None:
                    line = re.sub(' +', ' ', line)
                    line_info = line.split(" ")
                name = line_info[0]
                stock_one_element = {}
                stock_one_element["stock_name"] = name
                stock_one_element["up_down_percent"] = "unknow"
                stock_one_element["up_down_value"] = "unknow"
                stock_one_element["value"] = "unknow"
                stock_one_element["notify"] = False
                if len(line_info) == 5:
                    notify = line_info[1]
                    stock_one_element["limit"] = line_info[2]
                    stock_one_element["upper"] = line_info[3]
                    stock_one_element["lower"] = line_info[4]
                    if notify == "1":
                        stock_one_element["notify"] = True
                stock_list.append(stock_one_element)
        self.ui.update_stock_info(stock_list)
        zs_stock_list = []
        with open("zs_pool.txt", "r") as f:
            for number, line in enumerate(f,start=1):
                line = line.strip('\n').strip('\r')
                if line != None:
                    line = re.sub(' +', ' ', line)
                    line_info = line.split(" ")
                name = line_info[0]
                zs_stock_one_element = {}
                #  stock_list_dict["stock_name"] = self.get_stock_code_by_name(name)
                zs_stock_one_element["stock_name"] = line_info[0]
                zs_stock_one_element["up_down_percent"] = "unknow"
                zs_stock_one_element["value"] = "unknow"
                zs_stock_one_element["notify"] = False
                if len(line_info) == 3:
                    notify = line_info[1]
                    zs_stock_one_element["limit"] = line_info[2]
                    if notify == "1":
                        zs_stock_one_element["notify"] = True
                zs_stock_list.append(zs_stock_one_element)
        self.ui.update_zs_stock_info(zs_stock_list)
        compare_stock_list = []
        with open("compare_stock.txt", "r") as f:
            for number, line in enumerate(f,start=1):
                line = line.strip('\n').strip('\r')
                if line != None:
                    line = re.sub(' +', ' ', line)
                    line_info = line.split(" ")
                name_1 = line_info[0]
                name_2 = line_info[1]
                compare_stock_one_element = {}
                #  stock_list_dict["stock_name"] = self.get_stock_code_by_name(name)
                compare_stock_one_element["stock_name_1"] = line_info[0]
                compare_stock_one_element["up_down_percent_1"] = "unknow"
                compare_stock_one_element["stock_name_2"] = line_info[1]
                compare_stock_one_element["up_down_percent_2"] = "unknow"
                compare_stock_one_element["notify"] = False
                if len(line_info) == 4:
                    notify = line_info[2]
                    compare_stock_one_element["limit"] = line_info[3]
                    if notify == "1":
                        compare_stock_one_element["notify"] = True
                compare_stock_list.append(compare_stock_one_element)
        self.ui.update_compare_stock_info(compare_stock_list)

    def get_compare_info(self):
        stock_list = []
        float_up_down_1 = 0
        float_up_down_2 = 0
        with open("compare_stock.txt", "r") as f:
            stock_count = 0
            for number, line in enumerate(f,start=1):
                line = line.strip('\n')
                if line != None:
                    line = re.sub(' +', ' ', line)
                    line_info = line.split(" ")
                name_1 = line_info[0]
                name_2 = line_info[1]
                compare_one_element = {}
                stock_num=get_stock_num_by_name(name_1)
                if stock_num != None:
                    return_value = get_gupiao_info(stock_num[1])
                    if return_value != None:
                        (prePrice, price, time) = return_value
                        prePrice = float(prePrice)
                        price = float(price)
                        if price - prePrice > 0:
                            up_down_percent_num = str((price -prePrice) / prePrice * 100 + 0.005)[0:4]
                            up_down_percent = str((price -prePrice) / prePrice * 100 + 0.005)[0:4] + '%'
                            up_down_value = str(price - prePrice + 0.005)[0:4]
                        else:
                            up_down_percent_num = up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5]
                            up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5] + '%'
                            up_down_value = str(price - prePrice - 0.005)[0:5]
                        value = str(price)
                        compare_one_element["stock_name_1"] = name_1
                        compare_one_element["up_down_percent_1"] = up_down_percent
                        float_up_down_1 = float(up_down_percent_num)
                    else:
                        continue
                else:
                    continue
                stock_num=get_stock_num_by_name(name_2)
                if stock_num != None:
                    return_value = get_gupiao_info(stock_num[1])
                if return_value != None:
                    (prePrice, price, time) = return_value
                    prePrice = float(prePrice)
                    price = float(price)
                    if price - prePrice > 0:
                        up_down_percent_num = str((price -prePrice) / prePrice * 100 + 0.005)[0:4]
                        up_down_percent = str((price -prePrice) / prePrice * 100 + 0.005)[0:4] + '%'
                        up_down_value = str(price - prePrice + 0.005)[0:4]
                    else:
                        up_down_percent_num = up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5]
                        up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5] + '%'
                        up_down_value = str(price - prePrice - 0.005)[0:5]
                    value = str(price)
                    compare_one_element["stock_name_2"] = name_2
                    compare_one_element["up_down_percent_2"] = up_down_percent
                    float_up_down_2 = float(up_down_percent_num)
                else:
                    continue
                notify = self.ui.get_compare_notify_value(name_1, name_2)
                limit = self.ui.get_compare_limit_value(name_1, name_2)
                if notify == True:
                    try:
                        limit = float(limit)
                        if (abs(float_up_down_2 - float_up_down_1) > abs(limit)) and (limit != 0):
                            compare_notify(name_1 + name_2, name_1 + compare_one_element["up_down_percent_1"] + name_2 + compare_one_element["up_down_percent_2"], "mail")
                            compare_one_element["color"] = "red"
                    except:
                        pass
                stock_list.append(compare_one_element)
        self.ui.update_compare_stock_info(stock_list)

    def polling_get_stock_info(self):
        stock_list = []
        with open("stock_pool.txt", "r") as f:
            stock_count = 0
            for number, line in enumerate(f,start=1):
                line = line.strip('\n')
                if line != None:
                    line = re.sub(' +', ' ', line)
                    line_info = line.split(" ")
                name = line_info[0]
                if name.isdigit():
                    stock_num = (name, name)
                else:
                    stock_num=get_stock_num_by_name(name)
                if stock_num != None:
                    return_value = get_gupiao_info(stock_num[1])
                    if return_value != None:
                        (prePrice, price, time) = return_value
                        prePrice = float(prePrice)
                        price = float(price)
                        if price - prePrice > 0:
                            up_down_percent_num = str((price -prePrice) / prePrice * 100 + 0.005)[0:4]
                            up_down_percent = str((price -prePrice) / prePrice * 100 + 0.005)[0:4] + '%'
                            up_down_value = str(price - prePrice + 0.005)[0:4]
                        else:
                            up_down_percent_num = up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5]
                            up_down_percent = str((price -prePrice) / prePrice * 100 - 0.005)[0:5] + '%'
                            up_down_value = str(price - prePrice - 0.005)[0:5]
                        value = str(price)
                        stock_one_element = {}
                        stock_one_element["stock_name"] = name
                        stock_one_element["up_down_percent"] = up_down_percent
                        stock_one_element["up_down_value"] = up_down_value
                        stock_one_element["value"] = value
                        float_up_down = float(up_down_percent_num)
                        notify = self.ui.get_notify_value_by_name(name)
                        limit = self.ui.get_limit_value_by_name(name)
                        upper_value = self.ui.get_upper_value_by_name(name)
                        lower_value = self.ui.get_lower_value_by_name(name)
                        stock_count = stock_count + 1
                        if (notify == True):
                            try:
                                limit = float(limit)
                                if (abs(float_up_down) > abs(limit)) and (limit != 0):
                                    notify_user_message(stock_one_element["stock_name"], float_up_down, limit, notify_method)
                                    stock_one_element["color"] = "red"
                            except:
                                #print("limit exception here")
                                pass
                            try:
                                upper_value = float(upper_value)
                                if (price > upper_value) and (upper_value != 0):
                                    notify_user_message(stock_one_element["stock_name"], float_up_down, upper_value, notify_method)
                                    stock_one_element["color"] = "red"
                            except:
                                #print("uppper exception here")
                                pass
                            try:
                                lower_value = float(lower_value)
                                if (price < lower_value) and (upper_value != 0):
                                    notify_user_message(stock_one_element["stock_name"], float_up_down, lower_value, notify_method)
                                    stock_one_element["color"] = "red"
                            except:
                                #print("lower exception here")
                                pass
                        stock_list.append(stock_one_element)
        self.ui.update_stock_info(stock_list)

    def start(self):
        self._update()
    def _update(self):
        try:
            self.main_do_things()
            threading.Timer(1, self._update).start()
        except:
            print("something wrong happens, do the update again")
            time.sleep(3)
            self._update()
    def main_do_things(self):
        self.running_cnt = self.running_cnt + 1
        update_notify_when_new_day()
        if is_deal_time_now() == False:
            pass
        else:
            self.update_info_to_ui()

    def save_signal_slot(self, some_message):
        print("save signal", some_message)
        self.init_stock_info_from_file()
    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Escape:
            self.close()
        if e.key() == QtCore.Qt.Key_C:
            if e.modifiers() & QtCore.Qt.ControlModifier:
                print("ctrl + c press")
                self.close()

def exit_handle(signum, frame):
    print('You choose to stop me.')
    os.system('killall -9 qyh_stock_main_for_dfcf.py')
if __name__=='__main__':
    app=QApplication(sys.argv)
    signal.signal(signal.SIGINT, exit_handle)
    signal.signal(signal.SIGTERM, exit_handle)
    w=MyWidgets()
    #edit_w = UiEditWindow()
    edit_w = MyEditWindow()
    #w.ui.pushButton_3.clicked.connect(edit_w.open)
    edit_w.ui.c.save_signal.connect(w.save_signal_slot)
    #w.ui.c.w_save_signal(w.save_signal_slot)
    w.show()
    sys.exit(app.exec_())


"""
This module provides a progress bar for showing the user the progress of the
current step of the calculation
"""
from __future__ import division

from spam.exceptions import InternalError
from Tkinter import *

class ProgressBar(Toplevel):
   """ A progress bar window for SPAM calcs """
   WIDTH = 420
   HEIGHT = 40
   LW = 6 # width of the line
   def __init__(self, master, title, end_count=None):
      """ Constructor for ProgressBar """
      Toplevel.__init__(self, master)
      self.resizable(False, False)
      # Disable deleting
      self.protocol('WM_DELETE_WINDOW', lambda: 0)
      self.title(title)
      self.canv = Canvas(self, width=self.WIDTH, height=self.HEIGHT)
      self.count = 0
      # Pack the canvas
      self.canv.pack()
      if end_count is not None:
         self.initialize(end_count)
      else:
         self.withdraw()

   def initialize(self, end_count):
      """ Sets up a progress bar """
      self.end_count = end_count
      self.box = self.canv.create_rectangle(0, 0, self.WIDTH, self.HEIGHT,
                     width=self.LW)
      self.progress = self.canv.create_rectangle(self.LW, self.LW,
                     self.LW, self.HEIGHT-self.LW, fill='green4', width=0)
      self.text = self.canv.create_text(self.WIDTH//2, self.HEIGHT//2,
                     text='0 %')
      self.update_idletasks()
      self.deiconify()
      self.placeme()

   def placeme(self):
      """ This places the progress bar over the center of the master widget """
      import re
      geore = re.compile(r'(\d+)x(\d+)([+-]\d+)([+-]\d+)')

      self.master.update_idletasks()
      rematch = geore.match(self.master.winfo_geometry())
      i, j, masterxoff, masteryoff = rematch.groups()

      rematch = geore.match(self.geometry())
      myx, myy, i, j = rematch.groups()

      self.geometry('%sx%s%s%s' % (myx, myy, masterxoff, masteryoff))
      self.master.update_idletasks()

   def update(self):
      """ If we update our progress bar """
      self.count += 1
      self.print_method()

   def print_method(self):
      """ Here we update the progress bar """
      from time import sleep
      if not hasattr(self, 'end_count'):
         raise InternalError('Progress bar not initialized!')
      self.update_idletasks() # Let this flush to the screen
      self.canv.delete(self.progress)
      self.canv.delete(self.text)

      width, height = self.WIDTH - self.LW, self.HEIGHT - self.LW
      frac_done = self.count / self.end_count
      self.progress = self.canv.create_rectangle(self.LW, self.LW,
                        frac_done*width, height, fill='green4', width=0)
      self.text = self.canv.create_text(self.WIDTH//2, self.HEIGHT//2,
                        text = '%.1f %%' % (frac_done * 100))
      self.update_idletasks() # Let this flush to the screen

   def reset(self):
      """ Resets the progress bar """
      self.count = 0
      del self.end_count
      self.canv.delete(self.progress)
      self.canv.delete(self.text)
      self.withdraw()


'''
CS 330 - Artificial Intelligence
Homework 3 - Reinforcement Learning
Team Members:
    1. 2003101 - Adarsh Anand
    2. 2003104 - Aniket Chaudhri
'''

# valueIterationAgents.py
# -----------------------
# Licensing Information:  You are free to use or extend these projects for
# educational purposes provided that (1) you do not distribute or publish
# solutions, (2) you retain this notice, and (3) you provide clear
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
#
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
# The core projects and autograders were primarily created by John DeNero
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
# Student side autograding was added by Brad Miller, Nick Hay, and
# Pieter Abbeel (pabbeel@cs.berkeley.edu).

import collections
import mdp
import util

from learningAgents import ValueEstimationAgent

class ValueIterationAgent(ValueEstimationAgent):
    """
        * Please read learningAgents.py before reading this.*
        * Q1 *

        A ValueIterationAgent takes a Markov decision process
        (see mdp.py) on initialization and runs value iteration
        for a given number of iterations using the supplied
        discount factor.
    """

    def __init__(self, mdp, discount=0.9, iterations=100):
        """
          Your value iteration agent should take an mdp on
          construction, run the indicated number of iterations
          and then act according to the resulting policy.

          Some useful mdp methods you will use:
              mdp.getStates()
              mdp.getPossibleActions(state)
              mdp.getTransitionStatesAndProbs(state, action)
              mdp.getReward(state, action, nextState)
              mdp.isTerminal(state)
        """
        self.mdp = mdp
        self.discount = discount
        self.iterations = iterations
        self.values = util.Counter()
        self.runValueIteration()

    def runValueIteration(self):
        '''
            v[k](s) = max[a] Q[k](s, a)
            Q[k+1](s, a) = sum[s'] T(s, a, s') * [r(s, a, s') + discount*v[k](s')]

        '''
        "*** YOUR CODE HERE ***"
        # * Q1
        # for each iteration, the value of each state is updated using the bellman equation
        for i in range(self.iterations):
            iterationValues = util.Counter()  # Counter to hold the values for each iteration
            for s in self.mdp.getStates():  # for each state, the value is calculated as the max of q-values for all possible actions
                # if the state is terminal, the value is 0
                if self.mdp.isTerminal(s):
                    self.values[s] = self.mdp.getReward(
                        s, 'exit', '')  # reward for terminal state is 0
                else:
                    # getting all possible actions for a state
                    actions = self.mdp.getPossibleActions(s)
                    iterationValues[s] = max(
                        [self.computeQValueFromValues(s, a) for a in actions])  # getting the max q-value for a state
            self.values = iterationValues   # updating the values for the next iteration

    def getValue(self, state):
        """
          Return the value of the state (computed in __init__).
        """
        return self.values[state]

    def computeQValueFromValues(self, state, action):
        """
          Compute the Q-value of action in state from the
          value function stored in self.values.
        """
        "*** YOUR CODE HERE ***"
        # transitionStatesAndProbs = self.mdp.getTransitionStatesAndProbs(
        #     state, action)  # getting the transition states and probabilities for a state action pair
        # value = 0
        # for ts in transitionStatesAndProbs:  # for each transition state, the q-value is calculated as the sum of the transition probability and the discounted value of the next state
        #     stateTransitionReward = self.mdp.getReward(state, action, ts[0])
        #     value = value + stateTransitionReward + \
        #         self.discount * \
        #         (self.values[ts[0]]*ts[1]
        #          )  # q-value = reward + discount * value of next state
        #     # print value

        value = 0
        for ts in self.mdp.getTransitionStatesAndProbs(state, action):
            value += ts[1] * (self.mdp.getReward(state, action, ts[0]) + self.discount * self.values[ts[0]])

        return value

    def computeActionFromValues(self, state):
        """
          The policy is the best action in the given state
          according to the values currently stored in self.values.

          You may break ties any way you see fit.  Note that if
          there are no legal actions, which is the case at the
          terminal state, you should return None.
        """
        "*** YOUR CODE HERE ***"
        # initializing a stateAction counter, which is used to hold the q-value for each
        # state action pair. The policy or action is the one that gives the best expected sum of rewards.

        stateAction = util.Counter()
        # if the state is terminal, the policy is None
        if self.mdp.isTerminal(state):
            return None
        else:
            # getting all possible actions for a state
            actions = self.mdp.getPossibleActions(state)
            # for each action, the q-value is calculated
            for a in actions:
                stateAction[a] = self.computeQValueFromValues(state, a)
            # the action with the max q-value is returned
            return stateAction.argMax()

    def getPolicy(self, state):
        return self.computeActionFromValues(state)

    def getAction(self, state):
        "Returns the policy at the state (no exploration)."
        return self.computeActionFromValues(state)

    def getQValue(self, state, action):
        return self.computeQValueFromValues(state, action)

class AsynchronousValueIterationAgent(ValueIterationAgent):
    # * Q4
    """
        * Please read learningAgents.py before reading this.*
        An AsynchronousValueIterationAgent takes a Markov decision process
        (see mdp.py) on initialization and runs cyclic value iteration
        for a given number of iterations using the supplied
        discount factor.
    """

    def __init__(self, mdp, discount=0.9, iterations=1000):
        """
          Your cyclic value iteration agent should take an mdp on
          construction, run the indicated number of iterations,
          and then act according to the resulting policy. Each iteration
          updates the value of only one state, which cycles through
          the states list. If the chosen state is terminal, nothing
          happens in that iteration.
          Some useful mdp methods you will use:
              mdp.getStates()
              mdp.getPossibleActions(state)
              mdp.getTransitionStatesAndProbs(state, action)
              mdp.getReward(state)
              mdp.isTerminal(state)
        """
        ValueIterationAgent.__init__(self, mdp, discount, iterations)

    def runValueIteration(self):
        "*** YOUR CODE HERE ***"
        # Q4
        mdp = self.mdp
        states = mdp.getStates()

        for k in range(self.iterations):
            state = states[k % len(states)]
            possibleActions = mdp.getPossibleActions(state)
            if not possibleActions:  # if the state is terminal, the value is 0
                continue
            else:   # if the state is not terminal, the value is calculated as the max of q-values for all possible actions
                qValues = [self.computeQValueFromValues(state, action)
                           for action in possibleActions]   # getting the q-values for all possible actions
                # getting the max q-value for a state
                self.values[state] = max(qValues)

class PrioritizedSweepingValueIterationAgent(AsynchronousValueIterationAgent):
    """
        * Please read learningAgents.py before reading this.*
        A PrioritizedSweepingValueIterationAgent takes a Markov decision process
        (see mdp.py) on initialization and runs prioritized sweeping value iteration
        for a given number of iterations using the supplied parameters.
    """

    def __init__(self, mdp, discount=0.9, iterations=100, theta=1e-5):
        """
          Your prioritized sweeping value iteration agent should take an mdp on
          construction, run the indicated number of iterations,
          and then act according to the resulting policy.
        """
        self.theta = theta
        ValueIterationAgent.__init__(self, mdp, discount, iterations)

    def runValueIteration(self):
        "*** YOUR CODE HERE ***"
        predecessors = {}  # a dictionary to hold the predecessors of each state

        for state in self.mdp.getStates():  # for each state, the predecessors are calculated
            # if the state is not terminal, the predecessors are calculated
            if not self.mdp.isTerminal(state):
                # initializing the max value to -infinity
                maxVal = -float('inf')
                # for each action, the q-value is calculated
                for action in self.mdp.getPossibleActions(state):
                    # for each transition state, the q-value is calculated
                    for nextState, prob in self.mdp.getTransitionStatesAndProbs(state, action):

                        if nextState in predecessors:
                            # adding the state to the predecessors of the transition state
                            predecessors[nextState].add(state)
                        else:
                            # adding the state to the predecessors of the transition state
                            predecessors[nextState] = {state}

        pq = util.PriorityQueue()   # initializing a priority queue

        for s in self.mdp.getStates():  # for each state, the priority is calculated and added to the priority queue
            # if the state is not terminal, the priority is calculated
            if not self.mdp.isTerminal(s):
                maxQ = -float('inf')

                for action in self.mdp.getPossibleActions(s):
                    Q = self.computeQValueFromValues(s, action)

                    if Q > maxQ:
                        maxQ = Q
                diff = abs(maxQ - self.values[s])

                pq.update(s, -diff)

        # for each iteration, the state with the highest priority is popped from the priority queue
        for _ in range(self.iterations):
            if pq.isEmpty():    # if the priority queue is empty, the iteration is stopped
                break

            s = pq.pop()    # the state with the highest priority is popped from the priority queue

            if not self.mdp.isTerminal(s):
                maxVal = -float('inf')

                # for each action, the q-value is calculated
                for action in self.mdp.getPossibleActions(s):
                    Q = self.computeQValueFromValues(
                        s, action)   # the q-value is calculated
                    if Q > maxVal:
                        maxVal = Q

                self.values[s] = maxVal

            # for each predecessor of the state, the priority is calculated and added to the priority queue
            for p in predecessors[s]:
                maxQ = -float('inf')

                for action in self.mdp.getPossibleActions(p):
                    Q = self.computeQValueFromValues(p, action)

                    if Q > maxQ:    # the max q-value is calculated
                        maxQ = Q
                # the difference between the max q-value and the value of the state
                d = abs(maxQ - self.values[p])

                if d > self.theta:
                    # the priority is added to the priority queue
                    pq.update(p, -d)


from fastapi import APIRouter, Depends
from fastapi.responses import RedirectResponse, HTMLResponse
from dependencies import get_db
from db.schemas import QuizCreate, QuestionCreate, AnswerCreate
from tools import *
from json import dumps

quiz = APIRouter(prefix='/quiz')

@quiz.post('/', response_class=HTMLResponse)
async def lobby_post(request: Request, db: Session = Depends(get_db)):
    context = prepare_context(request)
    cookie = request.cookies.get('hash')
    user = get_user_by_hash(db, cookie)
    context.update({'user': user})
    context.update({'page_title': 'Lobby'})
    if request.query_params.get('created'):
        context.update({'created': True})
    quiz_list = get_quiz_list(db)
    if len(quiz_list) != 0:
        context.update({'quiz_list': quiz_list})
    return get_template("lobby.html", context)

@quiz.get('/', response_class=HTMLResponse)
async def lobby_get(request: Request, db: Session = Depends(get_db)):
    context = prepare_context(request)
    cookie = request.cookies.get('hash')
    user = get_user_by_hash(db, cookie)
    context.update({'user': user})
    if request.query_params.get('created'):
        context.update({'created': True})
    context.update({'page_title': 'Lobby'})
    quiz_list: List[models.Quiz] = get_quiz_list(db)
    if len(quiz_list) != 0:
        context.update({'quiz_list': quiz_list})
    return get_template("lobby.html", context)

@quiz.post('/search', response_class=HTMLResponse)
async def search(request: Request, db: Session = Depends(get_db)):
    context = prepare_context(request)
    cookie = request.cookies.get('hash')
    user = get_user_by_hash(db, cookie)
    context.update({'user': user})
    form = await form_to_obj(request)
    query = form['query']
    quiz_items = get_quiz_list(db)
    sorted_quiz_items = intelligent_search(query, quiz_items)
    context.update({'quiz_list': sorted_quiz_items})
    context.update({'page_title': f'Searching: {query}'})
    return get_template("lobby.html", context)

@quiz.get('/create', response_class=HTMLResponse)
async def create_quiz_get(request: Request):
    context = prepare_context(request)
    context.update({'page_title': 'Create quiz'})
    return get_template("create_quiz.html", context)

@quiz.post('/create', response_class=HTMLResponse)
async def create_quiz_post(request: Request, db: Session = Depends(get_db)):
    context = prepare_context(request)
    cookie = request.cookies.get('hash')
    user = get_user_by_hash(db, cookie)
    form = await form_to_obj(request)
    try:
        input_quiz = QuizCreate(**form, author_id=user.id)
        db_quiz = create_quiz(db, input_quiz)
        response = RedirectResponse(request.url_for('create_question_form'))
        response.set_cookie(key='quiz_id', value=db_quiz.id)
        return response
    except ValidationError as exc:
        context.update({'errors': [loc_by_exception(error) for error in exc.errors()]})
        context.update(**form)
    context.update({'page_title': 'Create quiz'})
    return get_template("create_quiz.html", context)

@quiz.post('/create/questions', response_class=HTMLResponse)
async def create_question_form(request: Request, db: Session = Depends(get_db)):
    back = RedirectResponse(request.url_for("lobby_get"))
    form = await form_to_obj(request)
    quiz_id = request.cookies.get('quiz_id')
    if not quiz_id:
        return back
    quiz_id = int(quiz_id)
    context = prepare_context(request)
    quiz = get_quiz_by_id(db, quiz_id)
    if not quiz:
        return back
    questions = get_questions_by_quiz_id(db, quiz_id)
    if len(questions) == 5:
        return back
    checked = form_has_key('check', form)
    answered = form_has_key('answer', form)
    if answered and checked:
        form_dict = question_form_to_dict(form)
        question_text = form_dict[0]
        question_schema = QuestionCreate(text=question_text,
                                         quiz_id=quiz_id)
        db_question = create_question(db, question_schema, quiz_id)
        answers_dict = form_dict[1]
        answers = []
        for answer in answers_dict:
            answer_schema = AnswerCreate(text=answer['text'],
                                         question_id=db_question.id,
                                         is_correct=answer['is_correct'])
            answers.append(answer_schema)
        db_answers = create_answers(db, answers, db_question.id)
        context.update({"success": True})
    elif answered and not checked:
        context.update({"error": True})
    else:
        pass
    context.update({'quiz_id': quiz_id})
    questions = get_questions_by_quiz_id(db, quiz_id)
    question_len = len(questions)
    if question_len == 5:
        response = RedirectResponse(request.url_for('lobby_get').include_query_params(created=True))
        response.delete_cookie(key='quiz_id')
        return response
    else:
        context.update({'count': len(questions)})
    context.update({'page_title': 'Create question'})
    return get_template("create_question.html", context)

@quiz.get('/show', response_class=HTMLResponse)
async def show_quiz_get(request: Request, db: Session = Depends(get_db)):
    back = RedirectResponse(request.url_for('lobby_get'))
    cookie = request.cookies.get('hash')
    user = get_user_by_hash(db, cookie)
    quiz_id = request.query_params.get('quiz_id')
    if not quiz_id:
        return back
    quiz_id = int(quiz_id)
    quiz: models.Quiz = get_quiz_by_id(db, quiz_id)
    if not quiz:
        return back
    context = prepare_context(request)
    author = get_user(db, quiz.author_id)
    context.update({'quiz': quiz})
    context.update({'author': author})
    context.update({'session_id': generate_session_id()})
    context.update({'user': user})
    context.update({'page_title': quiz.title})
    response = get_template("show_quiz.html", context)
    response = delete_quiz_info(response)
    return response

@quiz.post('/show', response_class=HTMLResponse)
async def show_questions(request: Request, db: Session = Depends(get_db)):
    quiz_id = request.cookies.get('quiz_id')
    quiz_id = int(quiz_id)
    current_question = request.cookies.get('current_question')
    current_question = int(current_question)
    context = prepare_context(request)
    questions = get_questions_by_quiz_id(db, quiz_id)
    question = questions[current_question - 1]
    context.update({'question': question})
    answers = get_answers_by_question_id(db, question.id)
    context.update({'answers': answers})
    context.update({'current_question': current_question})
    context.update({'page_title': f'Question {current_question}'})
    response = get_template('show_questions.html', context)
    return response

@quiz.post('/show/start', response_class=HTMLResponse)
async def start_quiz(request: Request):
    form = await form_to_obj(request)
    response = RedirectResponse(request.url_for('show_questions'))
    response.set_cookie(key='quiz_id', value=form['quiz_id'])
    response.set_cookie(key='current_question', value=form['current_question'])
    response.set_cookie(key='session_id', value=form['session_id'])
    return response

@quiz.post('/show/process', response_class=HTMLResponse)
async def process(request: Request, db: Session = Depends(get_db)):
    form = await form_to_obj(request)
    question_id = form['question_id']
    response = RedirectResponse(request.url_for('show_questions'))
    if not form_has_key('check', form):
        return response
    quiz_id = request.cookies.get('quiz_id')
    quiz_id = int(quiz_id)
    current_question = request.cookies.get('current_question')
    session_id = request.cookies.get('session_id')
    current_question = int(current_question)
    current_question += 1
    user = get_user_by_hash(db, request.cookies.get('hash'))
    marked = get_answers_ids(form)
    marked = dumps(marked)
    try:
        marked = schemas.MarkedBase(quiz_id=quiz_id, user_id=user.id, question_id=question_id, session_id=session_id,
                                    marked=marked)
        create_marked(db, marked)
    except ValidationError:
        return response
    if current_question > 5:
        return RedirectResponse(request.url_for('show_final'))
    response.set_cookie(key='current_question', value=str(current_question))
    return response

@quiz.post('/final', response_class=HTMLResponse)
async def show_final(request: Request, db: Session = Depends(get_db)):
    context = prepare_context(request)
    cookie = request.cookies.get('hash')
    quiz_id = request.cookies.get('quiz_id')
    quiz_id = int(quiz_id)
    user: models.User = get_user_by_hash(db, cookie)
    context.update({'user': user})
    session_id = request.cookies.get('session_id')
    user_marked: List[models.Session] = get_session_info(db, session_id)
    output = process_quiz(db, user_marked, user)
    context.update({'results': output})
    context.update({'page_title': 'Quiz results'})
    context.update({'user': user})
    response = get_template("show_final.html", context)
    response = delete_quiz_info(response)
    return response

@quiz.post('/delete', response_class=HTMLResponse)
async def delete_q(request: Request, db: Session = Depends(get_db)):
    form = await form_to_obj(request)
    if form['user_id'] == form['author_id']:
        delete_quiz(db, int(form['quiz_id']))
    return RedirectResponse(request.url_for('lobby_get'))


# -*- coding: utf-8 -*-
"""
Created on Wed Sep 25 16:52:48 2019

@author: Bohrium.Kwong
"""

from keras.models import load_model
from keras.optimizers import SGD
from keras.utils import to_categorical
from tqdm._tqdm import trange
import numpy as np
from skimage import io
from scipy.misc import imresize
import metrics
import matplotlib.pyplot as plt
from sklearn.metrics import classification_report, confusion_matrix
import gc
import glob
import os

class Test_epoch_from_array:
    r"""
    :param model: a model that loaded with keras
    :param input_array: a 4D numpy array [n,h,w,c] which includes n images transform to numpy array(RGB mode)
    :param batch_size: the value of batch_size in model's predicting
    :param verbose: show the information of tdqm method with dataloaders in processing or not
    """
    def __init__(self, model, input_array,batch_size=256,mean = [0,0,0], std = [1,1,1],verbose=False):
        self.model = model
        self.input_array = input_array
        self.batch_size = batch_size
        self.mean = mean
        self.std = std
        self.verbose = verbose

    def predict(self):
        self.input_array /= 255
        for i in range(3):
            self.input_array[:,:,:,i] -= self.mean[i]
            self.input_array[:,:,:,i] /= self.std[i]
        if self.input_array.shape[0] <= self.batch_size :
            output_predict =  self.model.predict(self.input_array,batch_size = self.batch_size)

        else:
            batch_count = self.input_array.shape[0]// self.batch_size
#            with tqdm(dataloaders[phase], desc = phase, file=sys.stdout, disable=not (self.verbose)) as iterator:
            for i in trange(batch_count + 1,disable=not (self.verbose)):
                top = i * self.batch_size
                bottom = min(self.input_array.shape[0], (i+1) * self.batch_size)
                if top < self.input_array.shape[0]:
                    output_predict_batch =  self.model.predict(self.input_array[top:bottom,:,:,:].copy(),batch_size = self.batch_size)
                    if i ==0 :
                        output_predict = output_predict_batch.copy()
                    else:
                        output_predict = np.row_stack((output_predict,output_predict_batch.copy()))
                    del image_tensor,output_predict_batch,output
                    gc.collect()
        return output_predict

def get_model_loaded(file_path,out_objects = False,objects_dict = None):
    """
    load the model trained by Keras base on the file name
    :param file_path: the saved model's file_path
    :param out_objects: True/False ,adds custom_objects or not in loadding model
    :param objects_dict: a dict that matches the model which you want to load
    :return: keras model object
    """
    if out_objects and objects_dict is not None:
        model = load_model(file_path,custom_objects=objects_dict)
    else:
        model = load_model(file_path)

    model.compile(loss='categorical_crossentropy',
              optimizer=SGD(lr=1e-2, momentum=0.1,decay=0.001, nesterov=True,clipvalue=0.7),
              metrics=['accuracy'])
    return model

def plot_confusion_matrix(test_labels, predictions,label_list):
    """
    the method of draw confusion_matrix
    """
    cm = confusion_matrix(test_labels, predictions)
    plt.imshow(cm, interpolation='nearest', cmap='Pastel1')
    plt.title('Confusion matrix', size=15)
    plt.colorbar()
    tick_marks = np.arange(3)
    plt.xticks(tick_marks, label_list,rotation=45, size=10)
    plt.yticks(tick_marks, label_list,size=10)
    plt.tight_layout()
    plt.ylabel('Actual label',size=15)
    plt.xlabel('Predicted label',size=15)
    width, height=cm.shape
    for x in range(width):
        for y in range(height):
            plt.annotate(str(cm[x][y]),xy=(y,x),horizontalalignment='center',verticalalignment='center')
    plt.show()

if __name__ == '__main__':

    np.set_printoptions(suppress=True)

    objects_dict = {'precision':metrics.precision,'recall':metrics.recall,'fmeasure':metrics.fscore}
    #如果模型有使用自定义的指标(metrics.*)，就需要定义objects_dict字典，以便进行加载
    model = get_model_loaded('Xception_299_.hdf5',out_objects = True,objects_dict = objects_dict)

    #以下是预测单张图片的方法
    I = io.imread('./test_png')
    I = np.expand_dims(I, axis=0)
    preds = model.predict_classes(I)
    print('Predicted:', preds)
    prob = model.predict_proba(I)
    print('Probality:', prob)
#    """
#    Predicted: [1]
#    Probality: [[0.00000055 0.9999994 ]]
#    """
    print(prob[0][1])
    # 0.9999994

    #以下是预测指定目录下多张图片的方法
    test_dataset_dir = '/test/'

    test_folders = ["/test/label_0/", "/test/label_1/"]

    data_set_name = test_folders[0].split("/")[-3]

    print("\nImages for Testing")
    print("=" * 30)

    test_images = []
    test_labels = []

    for index, folder in enumerate(test_folders):
        files = glob.glob(folder + "*.png")
        images = io.imread_collection(files)
#        images = [imresize(image, (299, 299)) for image in images] ### Reshape to (299, 299, 3) ###
        labels = [index] * len(images)
        test_images = test_images + images
        test_labels = test_labels + labels
        print("Class: %s. Size: %d" %(folder.split("/")[-2], len(images)))

    test_images = test_images.astype(np.float32) ### Standardise

    test_labels = np.array(test_labels).astype(np.int32)
    Y_test = to_categorical(test_labels, num_classes = np.unique(test_labels).shape[0])

    test_loss, test_accuracy = model.evaluate(test_images/255, Y_test, batch_size = 50)
    # 这里调用的是keras模型自带的evaluate方法进行评分

    print("\n" + test_folders[0].split("/")[1] + " Loss: %.3f" %(test_loss))
    print("\n" + test_folders[0].split("/")[1] + " Accuracy: %.3f" %(test_accuracy))
    print("=" * 30, "\n")

    posteriors = Test_epoch_from_array(model,test_images,256)

    predictions = np.argmax(posteriors, axis = 1)

    print(classification_report(test_labels, predictions, target_names = os.listdir(test_dataset_dir),
                                digits = len(os.listdir(test_dataset_dir))))
    # 打印classification_report

    plot_confusion_matrix(test_labels, predictions,os.listdir(test_dataset_dir))
    # 画混淆矩阵


import random
import warnings
from typing import Any, Iterator, List, Optional

import torch
from torch import Tensor

from avalanche.benchmarks.utils import make_classification_dataset
from avalanche.benchmarks.utils.data import AvalancheDataset
from avalanche.models import avalanche_forward
from avalanche.training.plugins.strategy_plugin import SupervisedPlugin
from avalanche.training.storage_policy import ClassBalancedBuffer
from avalanche.training.utils import cycle

class AGEMPlugin(SupervisedPlugin):
    """Average Gradient Episodic Memory Plugin.

    AGEM projects the gradient on the current minibatch by using an external
    episodic memory of patterns from previous experiences. If the dot product
    between the current gradient and the (average) gradient of a randomly
    sampled set of memory examples is negative, the gradient is projected.
    This plugin does not use task identities.
    """

    def __init__(self, mem_size: int, sample_size: int):
        """
        :param patterns_per_experience: number of patterns per experience in the
            memory.
        :param sample_size: number of patterns in memory sample when computing
            reference gradient.
        """

        super().__init__()

        self.sample_size = int(sample_size)

        self.storage_policy = ClassBalancedBuffer(max_size=mem_size, adaptive_size=True)
        self.buffer_dliter = None

        # Placeholder Tensor to avoid typing issues
        self.reference_gradients: Tensor = torch.empty(0)

    def before_training_iteration(self, strategy, **kwargs):
        """
        Compute reference gradient on memory sample.
        """

        if self.buffer_dliter is not None:
            strategy.model.train()
            strategy.optimizer.zero_grad()
            mb = self.sample_from_memory()
            xref, yref, tid = mb[0], mb[1], mb[-1]
            xref, yref = xref.to(strategy.device), yref.to(strategy.device)

            out = avalanche_forward(strategy.model, xref, tid)
            loss = strategy._criterion(out, yref)
            loss.backward()
            # gradient can be None for some head on multi-headed models
            reference_gradients_list = [
                p.grad.view(-1)
                if p.grad is not None
                else torch.zeros(p.numel(), device=strategy.device)
                for n, p in strategy.model.named_parameters()
            ]
            self.reference_gradients = torch.cat(reference_gradients_list)
            strategy.optimizer.zero_grad()

    @torch.no_grad()
    def after_backward(self, strategy, **kwargs):
        """
        Project gradient based on reference gradients
        """
        if self.buffer_dliter is not None:
            current_gradients_list = [
                p.grad.view(-1)
                if p.grad is not None
                else torch.zeros(p.numel(), device=strategy.device)
                for n, p in strategy.model.named_parameters()
            ]
            current_gradients = torch.cat(current_gradients_list)

            assert (
                current_gradients.shape == self.reference_gradients.shape
            ), "Different model parameters in AGEM projection"

            dotg = torch.dot(current_gradients, self.reference_gradients)
            if dotg < 0:
                alpha2 = dotg / torch.dot(
                    self.reference_gradients, self.reference_gradients
                )
                grad_proj = current_gradients - self.reference_gradients * alpha2

                count = 0
                for n, p in strategy.model.named_parameters():
                    n_param = p.numel()
                    if p.grad is not None:
                        p.grad.copy_(grad_proj[count : count + n_param].view_as(p))
                    count += n_param

    def after_training_exp(self, strategy, **kwargs):
        """Update replay memory with patterns from current experience."""
        self.storage_policy.update_from_dataset(strategy.experience.dataset)
        self.buffer_dliter = torch.utils.data.DataLoader(
            self.storage_policy.buffer, shuffle=True, batch_size=self.sample_size
        )
        self.buffer_dliter = cycle(self.buffer_dliter)

    def sample_from_memory(self):
        """
        Sample a minibatch from memory.
        Return a tuple of patterns (tensor), targets (tensor).
        """
        return next(self.buffer_dliter)


import abc
from contextlib import asynccontextmanager
import functools
import hashlib
import json
import typing
import warnings

from ponty.memo.lock.base import Lock, Locked
from ponty.registry import Registry

class Stampede(Locked):
    """Raised when the cache comes under high load and the lock times out.

    Inherits :class:`Locked <ponty.memo.Locked>`.

    """

class cachemiss:
    """Sentinel value representing a cache miss.

    Returned by subclasses of :class:`CacheStore`.

    """

_T = typing.TypeVar("_T")

class CacheStore(abc.ABC, typing.Generic[_T]):
    """Abstract Base Class. Container for cached items.

    :func:`cache` expects two abstract methods,
    :meth:`get <ponty.memo.CacheStore.get>` and
    :meth:`set <ponty.memo.CacheStore.set>`, and
    :func:`invalidate` expects one,
    :meth:`remove <ponty.memo.CacheStore.remove>`.
    All three must return awaitables.

    Generic on one variable, the type T of the cached item.

    """

    @abc.abstractmethod
    async def get(self, key: str) -> typing.Union[_T, type[cachemiss]]:
        """Fetch the cached value.

        Errors are not captured.

        :param key: unique id for the cached item
        :returns: the cached item,
          or the :class:`cachemiss` sentinel if it does not exist
        :rtype: Union[T, type[:class:`cachemiss`]]

        """

    @abc.abstractmethod
    async def set(self, key: str, data: _T) -> None:
        """Add an item to the cache.

        :param key: unique id for the item
        :param data: the item to be cached

        """

    @abc.abstractmethod
    async def remove(self, key: str) -> bool:
        """Remove an item from the cache.

        :param key: unique id for the cached item
        :returns: True if the item is found and removed, False otherwise

        """

_registry = Registry[tuple[CacheStore, Lock]]()

def cache(store: CacheStore, antistampede: Lock, name: str = ""):
    """Cache the decorated function's return value. Not thread-safe.

    As a rule of thumb, storage mechanics for a cache are
    implemented in `store`, and synchronization rules are handled
    by `antistampede`.
    See the :func:`localcache`
    `source ↗ <https://github.com/csira/ponty/blob/main/ponty/memo/cache/local.py>`__
    for an example.

    :param store: container, implementing get/set mechanics for cached items
    :param antistampede: instance of :class:`ponty.memo.Lock`;
      blocks on simultaneous access to the requested key to avoid a stampede
    :param name: if provided, registers the cache for use by
      :func:`invalidate`. Must be unique

    """
    def wraps(f):
        if name:
            _registry.add(name, (store, antistampede))

        @functools.wraps(f)
        async def wrapper(*a, **kw):
            key = _hash_args(*a, **kw)

            async with antistampede.lock(key):
                data = await store.get(key)
                if data is not cachemiss:
                    return data

                data = await f(*a, **kw)
                await store.set(key, data)

            return data

        return wrapper
    return wraps

@asynccontextmanager
async def invalidate(cachename: str, *a, **kw) -> typing.AsyncIterator[None]:
    """Context manager. Invalidates an item in the cache.

    Waits for then holds the cache's lock until the context manager exits.
    This prevents simultaneous fetches from re-cache-ing the item before
    mutations are committed. This also means functions calling 'invalidate'
    CANNOT be wrapped with a cache decorator, as the decorator will already
    hold the lock.

    :param cachename: name of the registered cache.
      Must match :func:`cache`'s `name` parameter

    Note the \*arg/\**kwarg combo must match the arguments to the
    cache-decorated function EXACTLY in order to get a key hit. E.g.,

    .. code-block:: python
        :emphasize-lines: 2,7,9

        @localcache(name="foo")
        async def get_foo(foo_id):
            ...

        async def update_foo(foo_id, ...):
            async with invalidate("foo", foo_id):  # this hits
                ...
            async with invalidate("foo", foo_id=foo_id):  # this does not
                ...

    """
    key = _hash_args(*a, **kw)

    try:
        store, antistampede = _registry.get(cachename)
    except KeyError:
        warnings.warn(f"'{cachename}' is not registered", category=RuntimeWarning)
        yield
    else:
        async with antistampede.lock(key):
            await store.remove(key)
            yield

def _hash_args(*a, **kw) -> str:
    args = a + tuple(sorted(kw.items()))
    encoded = json.dumps(args).encode("utf-8")
    return hashlib.sha1(encoded).hexdigest()


from __future__ import annotations

from pre_commit_hooks.check_yaml import yaml

def test_readme_contains_all_hooks():
    with open('README.md', encoding='UTF-8') as f:
        readme_contents = f.read()
    with open('.pre-commit-hooks.yaml', encoding='UTF-8') as f:
        hooks = yaml.load(f)
    for hook in hooks:
        assert f'`{hook["id"]}`' in readme_contents


# -*- coding: utf-8 -*-
"""Let constructs; do (imperative code in expression position)."""

# TODO: Update the @dlet, @dletseq, @dletrec, @blet, @bletseq, @bletrec examples
# TODO: to pass macro arguments using brackets once we bump to minimum Python 3.9.

from ...syntax import macros, test, test_raises  # noqa: F401
from ...test.fixtures import session, testset

from ...syntax import (macros, let, letseq, letrec, where,  # noqa: F401, F811
                       dlet, dletseq, dletrec,
                       blet, bletseq, bletrec,
                       do, do0, local, delete)

from ...seq import begin

x = "the global x"  # for lexical scoping tests

def runtests():
    with testset("do (imperative code in an expression)"):
        # Macro wrapper for unpythonic.seq.do (imperative code in expression position)
        #  - Declare and initialize a local variable with ``local[var << value]``.
        #    Is in scope from the next expression onward, for the (lexical) remainder
        #    of the do.
        #  - Assignment is ``var << value``. Valid from any level inside the ``do``
        #    (including nested ``let`` constructs and similar).
        #  - No need for ``lambda e: ...`` wrappers. Inserted automatically,
        #    so the lines are only evaluated as the underlying seq.do() runs.
        d1 = do[local[x << 17],
                print(x),
                x << 23,
                x]  # do[] returns the value of the last expression
        test[d1 == 23]

        # v0.14.0: do[] now supports deleting previously defined local names with delete[]
        a = 5
        d = do[local[a << 17],  # noqa: F841, yes, d is unused.
               test[a == 17],
               delete[a],
               test[a == 5],  # lexical scoping
               True]

        test_raises[KeyError, do[delete[a], ], "should have complained about deleting nonexistent local 'a'"]

        # do0[]: like do[], but return the value of the **first** expression
        d2 = do0[local[y << 5],  # noqa: F821, `local` defines the name on the LHS of the `<<`.
                 print("hi there, y =", y),  # noqa: F821
                 42]  # evaluated but not used
        test[d2 == 5]

    # Let macros. Lexical scoping supported.
    with testset("let, letseq, letrec basic usage"):
        # parallel binding, i.e. bindings don't see each other
        test[let[x << 17,
                 y << 23][  # noqa: F821, `let` defines `y` here.
                     (x, y)] == (17, 23)]  # noqa: F821

        # sequential binding, i.e. Scheme/Racket let*
        test[letseq[x << 1,
                    y << x + 1][  # noqa: F821
                        (x, y)] == (1, 2)]  # noqa: F821

        test[letseq[x << 1,
                    x << x + 1][  # in a letseq, rebinding the same name is ok
                        x] == 2]

        # letrec sugars unpythonic.lispylet.letrec, removing the need for quotes on LHS
        # and "lambda e: ..." wrappers on RHS (these are inserted by the macro):
        test[letrec[evenp << (lambda x: (x == 0) or oddp(x - 1)),  # noqa: F821, `letrec` defines `evenp` here.
                    oddp << (lambda x: (x != 0) and evenp(x - 1))][  # noqa: F821
                        evenp(42)] is True]  # noqa: F821

        # nested letrecs work, too - each environment is internally named by a gensym
        # so that outer ones "show through":
        test[letrec[z << 9000][  # noqa: F821
                 letrec[evenp << (lambda x: (x == 0) or oddp(x - 1)),  # noqa: F821
                        oddp << (lambda x: (x != 0) and evenp(x - 1))][  # noqa: F821
                            (evenp(42), z)]] == (True, 9000)]  # noqa: F821

    with testset("error cases"):
        # let is parallel binding, doesn't see the X in the same let
        test_raises[NameError,
                    let[X << 1,  # noqa: F821
                        y << X + 1][  # noqa: F821
                            print(X, y)],  # noqa: F821
                    "should not see the X in the same let"]

        test_raises[NameError,
                    letseq[X << y + 1,  # noqa: F821
                           y << 2][  # noqa: F821
                               (X, y)],  # noqa: F821
                    "y should not yet be defined on the first line"]

        test_raises[AttributeError,
                    let[x << 1,
                        x << 2][
                            print(x)],
                    "should not be able to rebind the same name in the same let"]

    # implicit do: an extra set of brackets denotes a multi-expr body
    with testset("implicit do (extra bracket syntax for multi-expr let body)"):
        a = let[x << 1,
                y << 2][[  # noqa: F821
                    y << 1337,  # noqa: F821
                    (x, y)]]  # noqa: F821
        test[a == (1, 1337)]

        # only the outermost extra brackets denote a multi-expr body
        a = let[(x, 1),
                (y, 2)][[  # noqa: F821
                    [1, 2]]]
        test[a == [1, 2]]

        # implicit do works also in letseq, letrec
        a = letseq[x << 1,
                   y << x + 1][[  # noqa: F821
                       x << 1337,
                       (x, y)]]  # noqa: F821
        test[a == (1337, 2)]

        a = letrec[x << 1,
                   y << x + 1][[  # noqa: F821
                       x << 1337,
                       (x, y)]]  # noqa: F821
        test[a == (1337, 2)]

    with testset("scoping, name shadowing"):
        # also letrec supports lexical scoping, since `letrec` expands inside out
        # (so the z in the inner scope expands to the inner environment's z,
        # which makes the outer expansion leave it alone):
        out = []
        letrec[z << 1][  # noqa: F821
            begin(out.append(z),  # noqa: F821
                  letrec[z << 2][  # noqa: F821
                      out.append(z)])]  # (be careful with the parentheses!)  # noqa: F821
        test[out == [1, 2]]

        # same using implicit do (extra brackets)
        out = []
        letrec[z << 1][[  # noqa: F821
                 out.append(z),  # noqa: F821
                 letrec[z << 2][  # noqa: F821
                     out.append(z)]]]  # noqa: F821
        test[out == [1, 2]]

        # lexical scoping: assignment updates the innermost value by that name:
        out = []
        letrec[z << 1][  # noqa: F821
            begin(out.append(z),  # outer z  # noqa: F821
                  # assignment to env is an expression, returns the new value
                  out.append(z << 5),  # noqa: F821
                  letrec[z << 2][  # noqa: F821
                      begin(out.append(z),         # inner z  # noqa: F821
                            out.append(z << 7))],  # update inner z  # noqa: F821
                  out.append(z))]  # outer z  # noqa: F821
        test[out == [1, 5, 2, 7, 5]]

        out = []
        letrec[x << 1][
            begin(out.append(x),
                  letrec[z << 2][  # noqa: F821
                      begin(out.append(z),  # noqa: F821
                            out.append(x << 7))],  # x only defined in outer letrec, updates that
                  out.append(x))]
        test[out == [1, 2, 7, 7]]

        # same using implicit do
        out = []
        letrec[x << 1][[
                 out.append(x),
                 letrec[z << 2][[  # noqa: F821
                     out.append(z),  # noqa: F821
                     out.append(x << 7)]],
                 out.append(x)]]
        test[out == [1, 2, 7, 7]]

        # letrec bindings are evaluated sequentially
        test[letrec[x << 1,
                    y << x + 2][  # noqa: F821
                        (x, y)] == (1, 3)]  # noqa: F821

        # so this is an error (just like in Racket):
        test_raises[AttributeError,
                    letrec[x << y + 1,  # noqa: F821, `y` being undefined here is the point of this test.
                           y << 2][  # noqa: F821
                               print(x)],
                    "y should not be yet defined on the first line"]

        # This is ok, because the y on the RHS of the definition of f
        # is evaluated only when the function is called.
        #
        # This is the whole point of having a letrec construct,
        # instead of just let, letseq.
        test[letrec[f << (lambda t: t + y + 1),  # noqa: F821
                    y << 2][  # noqa: F821
                        f(3)] == 6]  # noqa: F821

        # bindings are evaluated only once
        a = letrec[x << 1,
                   y << x + 2][[   # y computed now, using the current value of x  # noqa: F821
                       x << 1337,  # x updated now, no effect on y
                       (x, y)]]  # noqa: F821
        test[a == (1337, 3)]

        # lexical scoping: a comprehension or lambda in a let body
        # shadows names from the surrounding let, but only in that subexpr
        test[let[x << 42][[
                   [x for x in range(10)]]] == list(range(10))]
        test[let[x << 42][[
                   [x for x in range(10)],
                   x]] == 42]
        test[let[x << 42][
                   (lambda x: x**2)(10)] == 100]
        test[let[x << 42][[
                   (lambda x: x**2)(10),
                   x]] == 42]

    # let over lambda - in Python!
    with testset("let over lambda"):
        count = let[x << 0][
                      lambda: x << x + 1]
        test[count() == 1]
        test[count() == 2]

    # decorator version: let over def - a more pythonic approach?
    #   - sugar around unpythonic.lispylet.dlet et al.
    #   - env is passed implicitly, and named with a gensym (so lexical scoping works)
    with testset("let over def"):
        @dlet(x << 0)
        def count():
            x << x + 1  # assigment to let environment uses the "assignment expr" syntax
            return x
        test[count() == 1]
        test[count() == 2]

        # nested dlets respect lexical scoping
        @dlet(x << 22)
        def outer():
            x << x + 1
            @dlet(x << 41)
            def inner():
                return x << x + 1
            return (x, inner())
        test[outer() == (23, 42)]

        # letseq over def
        @dletseq(x << 1,
                 x << x + 1,
                 x << x + 2)
        def g(a):
            return a + x
        test[g(10) == 14]

        # letrec over def
        @dletrec(evenp << (lambda x: (x == 0) or oddp(x - 1)),  # noqa: F821, `dletrec` defines `evenp` here.
                 oddp << (lambda x: (x != 0) and evenp(x - 1)))  # noqa: F821
        def f(x):
            return evenp(x)  # noqa: F821
        test[f(42) is True]
        test[f(23) is False]

    with testset("let block"):
        # block version
        #   - the def takes no args, runs immediately, replaced with return value
        @blet(x << 21)
        def result():
            return 2 * x
        test[result == 42]

        @bletseq(x << 1,
                 x << x + 1,
                 x << x + 2)  # noqa: F823, `bletseq` defines and assigns to `x`.
        def result():
            return x
        test[result == 4]

        @bletrec(evenp << (lambda x: (x == 0) or oddp(x - 1)),  # noqa: F821, `bletrec` defines `evenp` here.
                 oddp << (lambda x: (x != 0) and evenp(x - 1)))  # noqa: F821
        def result():
            return evenp(42)  # noqa: F821
        test[result is True]

    # interaction of unpythonic's scoping system with Python's own lexical scoping
    with testset("integration of let scoping with Python's scoping"):
        x = "the nonlocal x"
        @dlet(x << "the env x")
        def test1():
            return x
        test[test1() == "the env x"]

        @dlet(x << "the env x")
        def test2():
            return x  # local var assignment not in effect yet  # noqa: F823, `dlet` defines `x` here.
            x = "the unused local x"  # noqa: F841, this `x` being unused is the point of this test.  # pragma: no cover
        test[test2() == "the env x"]

        @dlet(x << "the env x")
        def test3():
            x = "the local x"
            return x
        test[test3() == "the local x"]

        @dlet(x << "the env x")
        def test4():
            nonlocal x
            return x
        test[test4() == "the nonlocal x"]

        @dlet(x << "the env x")
        def test5():
            global x
            return x
        test[test5() == "the global x"]

        @dlet(x << "the env x")
        def test6():
            class Foo:
                x = "the classattr x"  # name in store context, not the env x
            return x
        test[test6() == "the env x"]

        @dlet(x << "the env x")
        def test7():
            class Foo:
                x = "the classattr x"
                def doit(self):
                    return self.x
            return (Foo().doit(), x)
        test[test7() == ("the classattr x", "the env x")]

        @dlet(x << "the env x")
        def test8():
            class Foo:
                x = "the classattr x"
                def doit(self):
                    return x  # no "self.", should grab the next lexically outer bare "x"
            return Foo().doit()
        test[test8() == "the env x"]

        # CAUTION: "del" is inherently a dynamic operation.
        #
        # Especially in "nonlocal x; del x" and "global x; del x", there is no
        # **lexical** section of code where the global/nonlocal x exists and
        # where it does not; the result depends on **when** those statements
        # are executed.
        #
        # For a local "del x", the situation is slightly simpler because inside one
        # scope, code runs top-down, statement by statement (or left-to-right,
        # expression by expression), but still there is the possibility of loops;
        # and in a "while", the loop condition is dynamic.
        #
        # For symmetry with how we treat assignments, we support "lexical deletion"
        # of local names: "del x" deletes x **for the lexically remaining part**
        # of the scope it appears in, and only if x has not been declared nonlocal
        # or global. "nonlocal x; del x" and "global x; del x" are ignored, by design.
        #
        # This is different from how Python itself behaves, but Python itself
        # doesn't have to resolve references statically at compile-time. ;)
        #
        # (What we do is actually pretty similar to what you get from symtable.symtable()
        # in the standard library, but we also perform some expression-by-expression
        # analysis to make it possible to refer to the old bindings on the RHS of
        # "name << value", as well as to support local deletion.)
        @dlet(x << "the env x")
        def test9():
            x = x + " (copied to local)"  # the local x = the env x  # noqa: F823
            return x  # the local x
        test[test9() == "the env x (copied to local)"]

        @dlet(x << "the env x")
        def test10():
            x = x + " (copied to local)"  # noqa: F823
            del x     # comes into effect for the next statement
            return x  # so this is env's original x
        test[test10() == "the env x"]

        @dlet(x << "the env x")
        def test11():
            x = "the local x"
            return x  # not deleted yet
            del x  # this seems to be optimized out by Python.  # pragma: no cover
        test[test11() == "the local x"]

        @dlet(x << "the env x")
        def test12():
            x = "the local x"
            del x
            x = "the other local x"
            return x
        test[test12() == "the other local x"]

        @dlet(x << "the env x")
        def test13():
            x = "the local x"
            del x
            return x  # noqa: F823, this `x` refers to the `x` in the `dlet` env.
            x = "the unused local x"  # noqa: F841, this `x` being unused is the point of this test.  # pragma: no cover
        test[test13() == "the env x"]

        with test_raises[NameError, "should have tried to access the deleted nonlocal x"]:
            x = "the nonlocal x"
            @dlet(x << "the env x")
            def test14():
                nonlocal x
                del x       # ignored by unpythonic's scope analysis, too dynamic
                return x    # trying to refer to the nonlocal x, which was deleted
            test14()
        x = "the nonlocal x"  # restore the test environment

        # in do[] (also the implicit do), local[] takes effect from the next item
        test[let[x << "the let x",
                 y << None][  # noqa: F821
                     do[y << x,                  # still the "x" of the let  # noqa: F821
                        local[x << "the do x"],  # from here on, "x" refers to the "x" of the do
                        (x, y)]] == ("the do x", "the let x")]  # noqa: F821

        # don't code like this! ...but the scoping mechanism should understand it
        result = []
        let[lst << []][do[result.append(lst),       # the let "lst"  # noqa: F821
                          local[lst << lst + [1]],  # LHS: do "lst", RHS: let "lst"  # noqa: F821
                          result.append(lst)]]      # the do "lst"  # noqa: F821
        test[result == [[], [1]]]

        # same using implicit do
        result = []
        let[lst << []][[result.append(lst),  # noqa: F821
                        local[lst << lst + [1]],  # noqa: F821
                        result.append(lst)]]  # noqa: F821
        test[result == [[], [1]]]

    with testset("haskelly syntax"):
        result = let[[foo << 5,  # noqa: F821, `let` defines `foo` here.
                      bar << 2]  # noqa: F821
                     in foo + bar]  # noqa: F821
        test[result == 7]

        result = letseq[[foo << 100,  # noqa: F821, `letseq` defines `foo` here.
                         foo << 2 * foo,  # noqa: F821
                         foo << 4 * foo]  # noqa: F821
                        in foo]  # noqa: F821
        test[result == 800]

        result = letrec[[evenp << (lambda x: (x == 0) or oddp(x - 1)),  # noqa: F821, `letrec` defines `evenp` here.
                         oddp << (lambda x: (x != 0) and evenp(x - 1))]  # noqa: F821
                        in [print("hi from letrec-in"),
                            evenp(42)]]  # noqa: F821
        test[result is True]

        # inverted let, for situations where a body-first style improves readability:
        result = let[foo + bar,  # noqa: F821, the names in this expression are defined in the `where` clause of the `let`.
                     where[foo << 5,  # noqa: F821, this defines `foo`.
                           bar << 2]]  # noqa: F821
        test[result == 7]

        result = letseq[foo,  # noqa: F821
                        where[foo << 100,  # noqa: F821
                              foo << 2 * foo,  # noqa: F821
                              foo << 4 * foo]]  # noqa: F821
        test[result == 800]

        # can also use the extra bracket syntax to get an implicit do
        # (note the [] should then enclose the body only).
        result = letrec[[print("hi from letrec-where"),
                         evenp(42)],  # noqa: F821
                        where[evenp << (lambda x: (x == 0) or oddp(x - 1)),  # noqa: F821
                              oddp << (lambda x: (x != 0) and evenp(x - 1))]]  # noqa: F821
        test[result is True]

        # With more than one binding the delimiters surrounding the bindings subform
        # are required, even in this format where they are somewhat redundant.
        result = let[[x << 1, y << 2] in x + y]  # noqa: F821
        test[result == 3]

    # single binding special syntax, no need for outer delimiters
    with testset("special syntax for single binding case (classic format)"):
        result = let[x, 1][2 * x]
        test[result == 2]
        result = let[(x, 1) in 2 * x]
        test[result == 2]
        result = let[2 * x, where(x, 1)]
        test[result == 2]

        @dlet(x, 1)
        def qux():
            return x
        test[qux() == 1]

        @dletseq(x, 1)
        def qux():
            return x
        test[qux() == 1]

        @dletrec(x, 1)
        def qux():
            return x
        test[qux() == 1]

        @blet(x, 1)
        def quux():
            return x
        test[quux == 1]

        @bletseq(x, 1)
        def quux():
            return x
        test[quux == 1]

        @bletrec(x, 1)
        def quux():
            return x
        test[quux == 1]

    with testset("special syntax for single binding case (modern format)"):
        result = let[x << 1][2 * x]
        test[result == 2]
        result = let[[x << 1] in 2 * x]
        test[result == 2]
        result = let[2 * x, where[x << 1]]
        test[result == 2]

        @dlet(x << 1)
        def qux():
            return x
        test[qux() == 1]

        @dletseq(x << 1)
        def qux():
            return x
        test[qux() == 1]

        @dletrec(x << 1)
        def qux():
            return x
        test[qux() == 1]

        @blet(x << 1)
        def quux():
            return x
        test[quux == 1]

        @bletseq(x << 1)
        def quux():
            return x
        test[quux == 1]

        @bletrec(x << 1)
        def quux():
            return x
        test[quux == 1]

    with testset("object instance bound to let variable"):
        # The point is to test whether `s.a` below transforms
        # correctly to `e.s.a`.
        class Silly:
            a = "Ariane 5"
        test[let[[s << Silly()] in s.a] == "Ariane 5"]  # noqa: F821

if __name__ == '__main__':  # pragma: no cover
    with session(__file__):
        runtests()


from flask import render_template
from flask_login import current_user
from datetime import datetime, date
from .models.match import Match
from flask import Blueprint, redirect, url_for, request
bp = Blueprint('waiting', __name__)

@bp.route('/waiting', methods=['POST', 'GET'])
def waiting():
    if not current_user.is_authenticated:
        return redirect(url_for('rankables.login'))
    now = datetime.now()

    # get all available products for sale:
    matches = Match.get_user_waiting_matches(current_user.rankable_id, now)

    return render_template('waiting.html', my_matches=matches)

@bp.route('/waiting_delete/<match_id>', methods=['POST', 'GET'])
def waiting_delete(match_id):
    Match.delete(match_id)
    now = datetime.now()
    matches = Match.get_user_waiting_matches(current_user.rankable_id, now)

    return render_template('waiting.html', my_matches=matches)

@bp.route('/accept/<match_id>', methods=['POST', 'GET'])
def accept(match_id):
    Match.accept(match_id)
    now = datetime.now()
    matches = Match.get_user_waiting_matches(current_user.rankable_id, now)

    return render_template('waiting.html', my_matches=matches)


# from django.core.mail import send_mail
# from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
from .models import Note

# def send_email_reminders():
#     now = datetime.now()
#     due_notes = Note.objects.filter(due_date__lte=now, reminder=True)
#     for note in due_notes:
#         send_mail(
#             'Reminder: ' + note.title,
#             note.body,
#             'mariamtestn@gmail.com',
#             [note.email],
#             fail_silently=False,
#         )

# def start_scheduler():
#     scheduler = BackgroundScheduler()
#     scheduler.add_job(send_email_reminders, 'interval', minutes=2)
#     scheduler.start()
# utils.py
from django.core.mail import send_mail

def send_email_reminders():
    notes = Note.objects.filter(due_date__lt=timezone.now(), reminder=True)

    for note in notes:
        subject = 'Reminder: ' + note.title
        message = 'Your note "{}" is due.'.format(note.title)
        from_email = 'mariamtestn@gmail.com'
        recipient_list = [note.email]

        send_mail(subject, message, from_email, recipient_list, fail_silently=False)