# -*- coding: utf-8 -*
# author: unknowwhite@outlook.com
# wechat: Ben_Xiaobai
# from os import add_dll_directory
import sys
# from threading import Event
# from traceback import print_exception
sys.path.append("./")
sys.setrecursionlimit(10000000)
from configs import admin,kafka
import time
from component.public_func import show_my_memory
from component.db_func import show_project,select_properties,insert_update_access_control_list
import json
import traceback
from configs.export import write_to_log

class access_control:

    def __init__(self,project=None):
        # self.ip_group = {}
        # self.ip = {}
        # self.distinct_id = {}
        self.projects = project if project else {}
        # self.customized = {}
        self.start_time = int(time.time())
        self.check_mem_start = int(time.time())
        self.my_memory = 0
        self.term_times = {'distinct_id':1,'add_on_key':admin.access_control_per_add_on_key,'ip':admin.access_control_distinct_id_per_ip,'ip_group':admin.access_control_distinct_id_per_ip*admin.access_control_ip_per_ip_group,'ip_group_extend':admin.access_control_distinct_id_per_ip*admin.access_control_ip_per_ip_group*admin.access_control_ip_group_per_ip_group_extend}
        self.type_int={'ip':60,'ip_group':61,'distinct_id':62,'add_on_key':63,'ip_group_extend':80}

    def check_mem(self):
        #每30秒检查一次内存占用量
        if int(time.time()-self.check_mem_start) <= 30 and self.projects != {}:
            return self.my_memory
        else:
            self.my_memory = int(show_my_memory())
            self.check_mem_start = int(time.time())
            return self.my_memory

    def refresh_threshold_list(self):
        self.threshold_list = {}
        project_list = show_project()[0]
        for project in project_list:
            if project[0] not in self.threshold_list:
                self.threshold_list[project[0]] = {}
            if project[4]:
                self.threshold_list[project[0]]['default_sum'] = project[4]
            if project[5]:
                self.threshold_list[project[0]]['default_event'] = project[5]
            event_threshold_list = select_properties(project=project[0])[0]
            for item in event_threshold_list:
                if item[0] not in self.threshold_list[project[0]]:
                    self.threshold_list[project[0]][item[0]] = item[1]
        # print(self.threshold_list)

    def insert_data(self,project,key,type_str,event,pv,hour,date):
        insert_update_access_control_list(project=project,key=key,type_int=self.type_int[type_str],event=event,pv=pv,date=date,hour=hour)

    def check_threshold(self,project,event):
        if project:
            if event == 'all':
                if 'all' in self.threshold_list[project]:
                    limit = self.threshold_list[project]['all']
                elif 'default_sum' in self.threshold_list[project]:
                    limit = self.threshold_list[project]['all']['default_sum']
                else:
                    limit =  admin.access_control_sum_count
            elif event != '' or event != ' ':
                if projects_project_event in self.threshold_list[projects_project]:
                    limit = self.threshold_list[projects_project][projects_project_event]
                elif 'default_event' in self.threshold_list[projects_project]:
                    limit = self.threshold_list[projects_project]['default_event']
                else:
                    limit = admin.access_control_event_default

    def etl(self):
        self.refresh_threshold_list()
        date = time.strftime("%Y-%m-%d", time.localtime())
        hour = int(time.strftime("%H", time.localtime()))
        for projects_project in self.projects:
            if projects_project in self.threshold_list:
                for projects_project_event in self.projects[projects_project]:
                    if self.projects[projects_project][projects_project_event] == 'all' :
                        #如果event是all的触发量。如果没有则使用全局总量
                        if 'all' in self.threshold_list[projects_project]:
                            limit = self.threshold_list[projects_project]['all']
                        elif 'default_sum' in self.threshold_list[projects_project]:
                            limit = self.threshold_list[projects_project]['all']['default_sum']
                        else:
                            limit =  admin.access_control_sum_count
                    elif self.projects[projects_project][projects_project_event] != '' :
                        #event不是all的触发量。如果没有，则使用项目阈值，如果再没有，则使用全局事件量
                        if projects_project_event in self.threshold_list[projects_project]:
                            limit = self.threshold_list[projects_project][projects_project_event]
                        elif 'default_event' in self.threshold_list[projects_project]:
                            limit = self.threshold_list[projects_project]['default_event']
                        else:
                            limit = admin.access_control_event_default
                    for term in self.projects[projects_project][projects_project_event]:
                        #比对项目与限制值
                        for content in self.projects[projects_project][projects_project_event][term]:
                            if self.projects[projects_project][projects_project_event][term][content] >= limit*self.term_times[term]*admin.access_control_max_window/3600:
                                self.insert_data(project=projects_project,key=content,type_str=term,event=projects_project_event,pv=self.projects[projects_project][projects_project_event][term][content],hour=hour,date=date)
            elif projects_project:
                for projects_project_event in self.projects[projects_project]:
                    if self.projects[projects_project][projects_project_event] == 'all' :
                        limit =  admin.access_control_sum_count
                    else:
                        limit = admin.access_control_event_default
                    for term in self.projects[projects_project][projects_project_event]:
                        #比对项目与限制值
                        for content in self.projects[projects_project][projects_project_event][term]:
                            if self.projects[projects_project][projects_project_event][term][content] >= limit*self.term_times[term]*admin.access_control_max_window/3600:
                                self.insert_data(project=projects_project,key=content,type_str=term,event=projects_project_event,pv=self.projects[projects_project][projects_project_event][term][content],hour=hour,date=date)
        self.projects = {}
        self.start_time = int(time.time())
        self.my_memory = int(show_my_memory())

    def update_data(self,project,event,term,content):
        if term not in self.projects[project]['all']:
            self.projects[project]['all'][term] = {}
        self.projects[project]['all'][term][content] = self.projects[project]['all'][term][content] + 1 if content in self.projects[project]['all'][term] else 1
        if event:
            if term not in self.projects[project][event]:
                self.projects[project][event][term] = {}
            self.projects[project][event][term][content] = self.projects[project][event][term][content] + 1 if content in self.projects[project][event][term] else 1

    def commit(self,project=None,event=None,ip_commit=None,distinct_id_commit=None,add_on_key_commit=None):
        if ip_commit:
            ip_group_commit = '.'.join(ip_commit.split('.')[0:3])
            ip_group_extend_commit = '.'.join(ip_commit.split('.')[0:2])
            self.update_data(project=project,event=event,term='ip',content=ip_commit)
            self.update_data(project=project,event=event,term='ip_group',content=ip_group_commit)
            self.update_data(project=project,event=event,term='ip_group_extend',content=ip_group_extend_commit)
        if distinct_id_commit:
            self.update_data(project=project,event=event,term='distinct_id',content=distinct_id_commit)
        if add_on_key_commit:
            self.update_data(project=project,event=event,term='add_on_key',content=add_on_key_commit)

    def traffic(self,project=None,event=None,ip_commit=None,distinct_id_commit=None,add_on_key_commit=None):
        if project and project not in self.projects:
            self.projects[project] = {}
        if 'all' not in self.projects[project]:
            self.projects[project]['all'] = {'ip_group':{},'ip':{},'distinct_id':{},'add_on_key':{}}
        if event not in self.projects[project]:
            self.projects[project][event] = {'ip_group':{},'ip':{},'distinct_id':{},'add_on_key':{}}
        if int(time.time())-self.start_time >= admin.access_control_max_window or self.check_mem() >= admin.access_control_max_memory:
            write_to_log(filename='access_control', defname='traffic', result='开始清理:'+str(self.check_mem()))
            self.etl()
            write_to_log(filename='access_control', defname='traffic', result='完成清理:'+str(self.check_mem()))
            self.traffic(project=project,event=event,ip_commit=ip_commit,distinct_id_commit=distinct_id_commit,add_on_key_commit=add_on_key_commit)
        else:
            self.commit(project=project,event=event,ip_commit=ip_commit,distinct_id_commit=distinct_id_commit,add_on_key_commit=add_on_key_commit)
            # print('commit')

if __name__ == "__main__":
    if admin.access_control_commit_mode =='access_control':
        from component.access_control import access_control
        from component.kafka_op import get_message_from_kafka
        ac_access_control = access_control()
        results = get_message_from_kafka(group_id=kafka.client_group_id+'_'+admin.access_control_kafka_client_group_id,client_id=kafka.client_id+'_'+admin.access_control_kafka_client_client_id)
        for item in results :
            group = json.loads(item.value.decode('utf-8'))['group'] if "group" in json.loads(item.value.decode('utf-8')) else None
            data = json.loads(item.value.decode('utf-8'))['data']
            offset = item.offset
            if group == 'event_track':
                try:
                    ac_access_control.traffic(project=data['project'],event=data['data_decode']['event'] if 'event' in data['data_decode'] else None,ip_commit=data['ip'],distinct_id_commit=data['data_decode']['distinct_id'],add_on_key_commit=data['data_decode']['properties'][admin.access_control_add_on_key] if admin.access_control_add_on_key in data['data_decode']['properties'] else None)
                except Exception:
                    error = traceback.format_exc()
                    write_to_log(filename='access_control', defname='main', result=error)


from translate import Translator
translator= Translator(to_lang="pt")
try:
	with open('C:/Users/LENOVO/Desktop/Translator/tarans.txt',mode = 'r') as my_file:
		text= my_file.read()
		translation = translator.translate(text)
		print(translation)
		with open('C:/Users/LENOVO/Desktop/Translator/tarans-ja.txt',mode='w' ) as my_file2:
			my_file2.write(translation)
except FileNotFoundError as err:
	print('check your path silly!')


#!/usr/bin/env python

import sys
import os
import django
import pytz
from datetime import datetime

print("I am alive!.")

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pim.settings")
django.setup()

from web.models import ProductData

#Some mappings for readability
EanCode=0
ProductDescription=1
NutritionDescription=2
IngredientsDescription=3
ProductName=4
CreationDate=5
LastmodifiedDate=6

tz = pytz.timezone("Europe/Stockholm")

testy = datetime.strptime("2007-10-27 12:24:12", "%Y-%m-%d %H:%M:%S")
print(testy)

lineset = open('temp/CoreProductData_stage180915.csv',encoding='utf-8').readlines()
headerrow =lineset.pop(0)
for line in lineset:
    print('')
    print(line)

    csv_row = line.split(';')

    _pd = ProductData(
        gtin=csv_row[EanCode],
        marketing_message=csv_row[ProductDescription].strip('"'),
        nutrition_description=csv_row[NutritionDescription].strip('"'),
        ingredient_description=csv_row[IngredientsDescription].strip('"'),
        name=csv_row[ProductName],
        creation_date=tz.localize(datetime.strptime(csv_row[CreationDate].strip('"'), "%Y-%m-%d %H:%M:%S")),
        last_modified=tz.localize(datetime.strptime(csv_row[LastmodifiedDate].strip('\n').strip('"'), "%Y-%m-%d %H:%M:%S"))
        )

    if _pd.marketing_message == "NULL":
        _pd.marketing_message = str()

    if _pd.nutrition_description == "NULL":
        _pd.nutrition_description = str()

    if _pd.ingredient_description == "NULL":
        _pd.ingredient_description = str()

    try:
        _pd.save()
    except Exception as e:
        print(e)
        pass


from io import BytesIO
import multiprocessing
import streamlit as st
from XOR_file_enc_threading import XOR_encryption, XOR_decryption

# streamlit run app.py 2>NUL

st.title('XOR Cipher')
st.header('FILE ENCRYPTION USING XOR CIPHER')
st.write("""
File encryption using the XOR cipher is a method of securing the contents of a file by applying a simple encryption algorithm known as the XOR (exclusive OR) cipher. The XOR cipher is a symmetric encryption algorithm that operates on binary data, where each byte (or bit) in the file is bitwise XORed with a key, typically a sequence of bytes (or bits).

The XOR operation works by comparing the corresponding bits of two operands (in this case, the file data and the encryption key), and producing an output bit that is set to 1 if the two input bits are different (i.e., one is 0 and the other is 1), and 0 if the input bits are the same (i.e., both 0 or both 1). This operation is performed on each byte (or bit) of the file data, using the corresponding byte (or bit) of the encryption key, which is repeated cyclically to match the length of the file.

To encrypt a file using XOR cipher, each byte in the file is bitwise XORed with a corresponding byte from the encryption key. This process is reversible, meaning that the original file can be decrypted by applying the same XOR operation using the same encryption key. However, the security of the XOR cipher is relatively weak, as it is susceptible to various attacks, such as frequency analysis and known-plaintext attacks, and is not recommended for strong encryption requirements.
""")
st.write('---')

# Widgets
st.header('Inputs')

select_option = st.selectbox('Pick one', ['Encrypt', 'Decrypt'])

# Create a file uploader widget
if select_option:
    if select_option == 'Encrypt':
        uploaded_file = st.file_uploader("Choose a file", type=None)
    elif select_option == 'Decrypt':
        uploaded_file = st.file_uploader("Choose a file", type=['.enc'])
    else:
        pass

btn_disabled = True
Has_file = False
Has_key = False
Except_file =".enc"

# Check if a file has been uploaded
file_name = ""
if uploaded_file is not None:
    file_contents = uploaded_file.read()
    file_name = uploaded_file.name
    st.write(f"Filename: {file_name}")
    st.write(f"File size: {len(file_contents)} bytes")
    Has_file = True

key = st.text_input("KEY: ")
if key != "":
    Has_key = True
if Has_key and Has_file:
    btn_disabled = False

btn_submit = st.button(f'{select_option}', disabled = btn_disabled)

if select_option:
    if btn_submit:
        file_output =""
        st.write(f"Filename: {file_name}")
        st.write(f"key {key}")
        if select_option == "Encrypt":
            file = XOR_encryption(file_contents, key)
            file_output = file_name+".enc"
        if select_option == "Decrypt":
            file = XOR_decryption(file_contents, key)
            file_output = "decrypted_"+file_name[:-4]

        print(f"Processing by {multiprocessing.cpu_count()} CPUs...")
        # Convert bytearray to BytesIO object
        file = BytesIO(file)

        # Set the file name
        file_name_output = file_output

        # Create a download button
        st.download_button(label='Download File', data=file, file_name=file_name_output, mime='application/octet-stream')

# phrase = st.text_input("FILE: ")
# shift_dir = st.selectbox("Shift direction: ", ('Forward', 'Backward'))
# shift_no = st.number_input("Shift Amount: ", min_value=1)


import logging
import sqlite3
import __init__  # noqa pylint: disable=W0611

from time import sleep

logger = logging.getLogger(__name__)

class Inventory:
    def __init__(self):  # noqa
        logger.info("Connecting to database")
        self.connection = sqlite3.connect("products.db")
        self.connection.set_trace_callback(logger.debug)
        logger.debug("Getting cursor")
        self.cursor = self.connection.cursor()

        self.init_table()

    @property
    def columncount(self):
        return 4  # this needs to be updated when init_table is changed

    @property
    def rowcount(self):
        self.execute("SELECT COUNT(id) FROM inventory")
        return self.cursor.fetchone()[0]

    def get_ids(self):
        self.execute("SELECT id FROM inventory ORDER BY id")
        return [i[0] for i in self.cursor.fetchall()]

    def get_name_from_id(self, id):
        self.execute("SELECT name FROM inventory WHERE id = :id", {"id": id})
        return self.cursor.fetchone()[0]

    def get_price_from_id(self, id):
        self.execute("SELECT price FROM inventory WHERE id = :id", {"id": id})
        return self.cursor.fetchone()[0]

    def get_amount_from_id(self, id):
        self.execute("SELECT amount FROM inventory WHERE id = :id", {"id": id})
        return self.cursor.fetchone()[0]

    def init_table(self):
        # if you are changing this, do not forget to update rowcount
        logger.debug("Ensuring table layout is correct")
        self.execute_commit(
            """
            CREATE TABLE IF NOT EXISTS inventory (
                id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
                name VARCHAR(30) NOT NULL,
                amount INTEGER DEFAULT 0,
                price FLOAT DEFAULT NULL,
                CHECK (name != ''),
                CHECK (amount >= 0),
                CHECK (price >= 0)
            )
            """
        )

    def execute_commit(self, command, values=None):
        self.execute(command, values)
        self.commit()

    def get_id_name_pairs(self):
        self.execute("SELECT id, name FROM inventory ORDER BY id")
        return self.cursor.fetchall()

    def execute(self, command, values=None):
        if values is None:
            values = {}
        logger.debug(
            "Executing command '%s' with values '%s'",
            self.clean_command(command),
            values,
        )
        self.cursor.execute(command, values)
        affected_rows = self.cursor.rowcount
        if affected_rows > -1:
            logger.debug("Affected rows: %s", affected_rows)
            print("Affected items:", affected_rows)

    def commit(self):
        logger.debug("Committing transaction")
        self.connection.commit()

    def close(self):
        logger.info("Closing database connection")
        self.connection.close()

    def clean_command(self, command):
        lines = command.splitlines()
        ret = ""
        for line in lines:
            line = line.strip().replace("\n", "")
            line += " " if line.endswith(",") else ""
            ret += line
        return ret

    def new_item(self, name, price, amount = None):
        logger.info("Creating new item '%s' with price '%s'", name, price)
        command = """INSERT INTO inventory (name, price, amount) VALUES (:name, :price, :amount)"""
        values = {"name": name, "price": price, "amount": amount}
        self.execute_commit(command, values)
        return self.cursor.lastrowid

    def get_id_from_name(self, name):
        text = "get_id_from_name is DEPRECATED and will always return 0, hopefully crashing the program. It was removed to allow for duplicate product names"
        logger.critical(text)
        print(text)
        return 0

    def display_item(self, id):
        if id is None:
            print("No corresponding item")
            return
        self.execute(
            "SELECT id, name, price, amount FROM inventory WHERE id = :id ORDER BY id",
            {"id": id},
        )
        id, name, price, amount = self.cursor.fetchone()
        if price is None:
            price = "-"
        print("{:<10}{:<30}{:<13}{}".format(id, name, price, amount))

    def display_header(self):
        print("id        name                          price        amount")

    def modify_item(self, id, name, price):
        logger.info("Item '%s' now has name '%s' and price '%s'", id, name, price)
        self.execute_commit(
            "UPDATE inventory SET name = :name, price = :price WHERE id = :id",
            {"name": name, "price": price, "id": id},
        )

    def set_price(self, id, price):
        self.execute_commit(
            "UPDATE inventory SET price = :price WHERE id = :id",
            {"price": price, "id": id},
        )

    def set_name(self, id, name):
        self.execute_commit(
            "UPDATE inventory SET name = :name WHERE id = :id", {"name": name, "id": id}
        )

    def set_amount(self, id, amount):
        self.execute_commit(
            "UPDATE inventory SET amount = :amount WHERE id = :id",
            {"amount": amount, "id": id},
        )

    def sell_item(self, id, amount):
        self.change_amount(id, -amount)

    def buy_item(self, id, amount):
        self.change_amount(id, amount)

    def change_amount(self, id, amount):
        self.execute("SELECT amount FROM inventory WHERE id = :id", {"id": id})
        old_amount = self.cursor.fetchone()[0]
        new_amount = old_amount + amount
        assert new_amount >= 0
        logger.info(
            "Item '%s' now has amount '%s' from amount '%s'", id, new_amount, old_amount
        )
        self.execute_commit(
            "UPDATE inventory SET amount = :amount WHERE id = :id",
            {"amount": new_amount, "id": id},
        )

    def list_all(self):
        self.display_header()
        self.execute("SELECT id FROM inventory ORDER BY id")
        ids = []
        id = self.cursor.fetchone()
        while id:
            ids.append(id[0])
            id = self.cursor.fetchone()
        for id in ids:
            self.display_item(id)
        if ids == []:
            print("No corresponding items found")

    def delete(self, id):
        logger.info("Deleting item '%s'", id)
        self.execute_commit("DELETE FROM inventory WHERE id = :id", {"id": id})

    def query(self, query):
        logger.warning("Using query is unsafe!")
        logger.warning("Got query '%s'", query)
        logger.warning("This query may not be safe!")
        logger.warning("This allows the user to execute arbitrary SQL operations!")
        self.execute_commit(query)
        ids = []
        id = self.cursor.fetchone()
        while id:
            ids.append(id[0])
            id = self.cursor.fetchone()
        if ids == []:
            print("No items in inventory.")
        else:
            self.display_header()
            for id in ids:
                self.display_item(id)


import cv2
import numpy as np
import time
import imutils
from pyimagesearch.panorama import Stitcher

left =cv2.VideoCapture("http://192.168.43.1:8080/video")
right =cv2.VideoCapture("http://192.168.43.180:8080/video")
while True:
    start = time.time()
    left_check, left_frame = left.read()
    right_check, right_frame = right.read()
    # stitching code below this

    # load the two images and resize them to have a width of 400 pixels
    # (for faster processing)
    imageA = left_frame
    imageB = right_frame

    imageA = imutils.resize(imageA, width=400)
    imageB = imutils.resize(imageB, width=400)

    # stitch the images together to create a panorama
    stitcher = Stitcher()
    (result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)

    cv2.imshow("Image A", imageA)
    cv2.imshow("Image B", imageB)
    cv2.imshow("Result", result)
    end = time.time()
    print(end - start)
    #time.sleep(1)
    key = cv2.waitKey(1)
    if(key==ord('q')):
        break

left.release()
right.release()
cv2.destroyAllWindows()


from collections import deque
import sys
input = sys.stdin.readline

dx = [0,1,-1,0]
dy = [1,0,0,-1]

def bfs(hx, hy, ex, ey):
    queue = deque()
    queue.append([hx,hy,0])
    visited = [[[-1]*m for _ in range(n)] for __ in range(2)]
    visited[0][hy][hx] = 0

    while queue:
        x,y,use = queue.popleft()
        if x == ex and y == ey:
            return visited[use][y][x]

        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if nx < 0 or ny < 0 or nx >= m or ny >= n:
                continue

            if not board[ny][nx] and visited[use][ny][nx] == -1:
                queue.append([nx, ny, use])
                visited[use][ny][nx] = visited[use][y][x] + 1
            # 보드가 1이고, 아직 부수지 않았고, 방문하지 않았다면

            elif use == 0 and visited[use+1][ny][nx] == -1:
                queue.append([nx,ny, use+1])
                visited[use+1][ny][nx] = visited[use][y][x] + 1

    return -1

n,m = map(int,input().split())
hx,hy = map(int,input().split())
ex,ey = map(int,input().split())
board = [list(map(int,input().split())) for _ in range(n)]

print(bfs(hy-1,hx-1,ey-1,ex-1))


from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http.response import HttpResponseRedirect
from django.urls.base import reverse
from django.views.generic.edit import ProcessFormView
from edc_appointment.models.appointment import Appointment
from edc_constants.constants import YES
from edc_lab.models import Consignee
from edc_lab.models.model_mixins import RequisitionModelMixin
from edc_label.job_result import JobResult
from edc_label.printers_mixin import PrintersMixin
from edc_metadata.constants import REQUIRED, KEYED
from edc_metadata.models import RequisitionMetadata

from ..requisition_report import RequisitionReport
from ..requisition_labels import RequisitionLabels

class RequisitionPrintActionsView(LoginRequiredMixin, PrintersMixin, ProcessFormView):

    job_result_cls = JobResult
    requisition_report_cls = RequisitionReport
    requisition_labels_cls = RequisitionLabels
    success_url = settings.DASHBOARD_URL_NAMES.get('subject_dashboard_url')
    print_selected_button = 'print_selected_labels'
    print_all_button = 'print_all_labels'
    print_requisition = 'print_requisition'
    checkbox_name = 'selected_panel_names'

    def __init__(self, **kwargs):
        self._appointment = None
        self._selected_panel_names = []
        self._requisition_metadata = None
        self._requisition_model_cls = None
        super().__init__(**kwargs)

    def post(self, request, *args, **kwargs):
        response = None
        if self.selected_panel_names:
            if self.request.POST.get('submit') in [
                    self.print_all_button, self.print_selected_button]:
                self.print_labels_action()
            elif self.request.POST.get('submit_print_requisition'):
                self.consignee = Consignee.objects.get(
                    pk=self.request.POST.get('submit_print_requisition'))
                response = self.render_manifest_to_response_action()
        if not response:
            subject_identifier = request.POST.get('subject_identifier')
            success_url = reverse(self.success_url, kwargs=dict(
                subject_identifier=subject_identifier,
                appointment=str(self.appointment.pk)))
            response = HttpResponseRedirect(redirect_to=f'{success_url}')
        return response

    def print_labels_action(self):

        labels = self.requisition_labels_cls(
            requisition_metadata=self.requisition_metadata.filter(
                panel_name__in=self.selected_panel_names),
            panel_names=self.selected_panel_names,
            appointment=self.appointment,
            user=self.request.user)

        if labels.zpl_data:
            job_id = self.clinic_label_printer.stream_print(
                zpl_data=labels.zpl_data)
            job_result = self.job_result_cls(
                name=labels.label_template_name, copies=1, job_ids=[job_id],
                printer=self.clinic_label_printer)
            messages.success(self.request, job_result.message)
        if labels.requisitions_not_printed:
            panels = ', '.join(
                [str(r.panel_object) for r in labels.requisitions_not_printed])
            messages.warning(
                self.request,
                f'Some selected labels were not printed. See {panels}.')

    def render_manifest_to_response_action(self):
        panel_names = [r.panel.name for r in self.verified_requisitions]
        if panel_names:
            requisition_report = self.requisition_report_cls(
                appointment=self.appointment,
                selected_panel_names=panel_names,
                consignee=self.consignee,
                request=self.request)
            response = requisition_report.render()
        else:
            messages.error(
                self.request, 'Nothing to do. No "verified" requisitions selected.')
            response = None
        return response

    @property
    def requisition_metadata(self):
        """Returns a queryset of keyed or required RequisitionMetadata for this
        appointment.
        """
        if not self._requisition_metadata:
            appointment = Appointment.objects.get(
                pk=self.request.POST.get('appointment'))
            subject_identifier = self.request.POST.get('subject_identifier')
            opts = dict(
                subject_identifier=subject_identifier,
                visit_schedule_name=appointment.visit_schedule_name,
                schedule_name=appointment.schedule_name,
                visit_code=appointment.visit_code,
                visit_code_sequence=appointment.visit_code_sequence)
            self._requisition_metadata = RequisitionMetadata.objects.filter(
                entry_status__in=[KEYED, REQUIRED], **opts)
        return self._requisition_metadata

    @property
    def selected_panel_names(self):
        """Returns a list of panel names selected on the page.

        Returns all on the page if "print all" is submitted.
        """
        if not self._selected_panel_names:
            if self.request.POST.get('submit') == self.print_all_button:
                for metadata in self.requisition_metadata:
                    self._selected_panel_names.append(metadata.panel_name)
            else:
                self._selected_panel_names = self.request.POST.getlist(
                    self.checkbox_name) or []
        return self._selected_panel_names

    @property
    def appointment(self):
        if not self._appointment:
            self._appointment = Appointment.objects.get(
                pk=self.request.POST.get('appointment'))
        return self._appointment

    @property
    def requisition_model_cls(self):
        if not self._requisition_model_cls:
            for v in self.appointment.visit_model_cls().__dict__.values():
                try:
                    model_cls = getattr(getattr(v, 'rel'), 'related_model')
                except AttributeError:
                    pass
                else:
                    if issubclass(model_cls, RequisitionModelMixin):
                        self._requisition_model_cls = model_cls
        return self._requisition_model_cls

    @property
    def verified_requisitions(self):
        """Returns a list of "verified" requisition model instances related
        to this appointment.
        """
        verified_requisitions = []
        for k, v in self.appointment.visit_model_cls().__dict__.items():
            try:
                model_cls = getattr(getattr(v, 'rel'), 'related_model')
            except AttributeError:
                pass
            else:
                if issubclass(model_cls, RequisitionModelMixin):
                    verified_requisitions.extend(
                        list(getattr(self.appointment.visit, k).filter(
                            clinic_verified=YES)))
        return verified_requisitions


from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

tasks = []

class Task:
    def __init__(self, description):
        self.description = description
        self.completed = False

@app.route('/')
def index():
    return render_template('index.html', tasks=tasks)

@app.route('/add_task', methods=['POST'])
def add_task():
    task_description = request.form.get('task')
    if task_description:
        task = Task(task_description)
        tasks.append(task)
    return redirect(url_for('index'))

@app.route('/delete_task/<int:task_index>')
def delete_task(task_index):
    if 0 <= task_index < len(tasks):
        tasks.pop(task_index)
    return redirect(url_for('index'))

@app.route('/toggle_task/<int:task_index>')
def toggle_task(task_index):
    if 0 <= task_index < len(tasks):
        tasks[task_index].completed = not tasks[task_index].completed
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)


"""
Cedric Pereira, Steven Hurkett, Zack Bowles-Lapointe
December 6 2023
Weather App - User Interaction
"""

import sqlite3
import json
from datetime import datetime
from scrape_weather import WeatherScraper
from db_operations import DBOperations
from plot_operations import PlotOperations

class WeatherProcessor:
    """
    Represents the Weather Processor where user feedback is given.
    """
    def __init__(self):
        self.conn = sqlite3.connect('weather.db')
        self.cursor = self.conn.cursor()

    def weather_menu(self):
        """
        This is the menu for the weather processor.
        """
        print("Weather Data")
        print("1. Download Full Weather Data.")
        print("2. Update Weather Data.")
        print("3. Show BoxPlot")
        print("4. Show LinePlot")
        print("5. Exit")

    def full_pull(self):
        """
        This does a full pull from todays date to the earliest date.
        """
        print("Getting fresh data...")

        today = datetime.now().date()
        text_file = "test.txt"
        url = f'https://climate.weather.gc.ca/climate_data/daily_data_e.html?timeframe=2&StationID=27174&EndYear=1996&EndMonth=10&StartYear={today.year}&StartMonth={today.month}&Year={today.year}&Month={today.month}&Day={1}'
        scraper = WeatherScraper(url)
        scraper.scrape_data()
        operations = DBOperations("weather.db")
        operations.initialize_db()
        operations.purge_data()
        with open(text_file, "r") as text_data:
            json_data = json.load(text_data)
            operations.save_data(json_data)
        for date in operations.fetch_data():
            print(f"Sample Date: {date[0]}, Location: {date[1]}, Min Temp: {date[2]}, Max Temp: {date[3]}, Average Temp: {date[4]}")
        print("Full download completed")

    def update_weather(self):
        """
        This is used to update the weather data without scraping it all again.
        """
        print("Updating weather data, please wait...")
        today = datetime.now().date()
        self.cursor.execute("SELECT sample_date FROM weather ORDER BY DATE(sample_date) DESC LIMIT 1")
        last_update_string = self.cursor.fetchone()[0]
        last_month_raw = last_update_string[5:7]
        last_month = last_month_raw.rstrip('-')
        last_year = last_update_string[0:4]
        text_file = "test.txt"
        url = f'https://climate.weather.gc.ca/climate_data/daily_data_e.html?timeframe=2&StationID=27174&EndYear={last_year}&EndMonth={last_month}&StartYear={today.year}&StartMonth={today.month}&Year={today.year}&Month={today.month}&Day={1}'
        scraper = WeatherScraper(url)
        scraper.scrape_data()
        operations = DBOperations("weather.db")
        operations.initialize_db()

        with open(text_file, "r") as text_data:
            json_data = json.load(text_data)
            operations.save_data(json_data)

        for date in operations.fetch_data():
            print(f"Sample Date: {date[0]}, Location: {date[1]}, Min Temp: {date[2]}, Max Temp: {date[3]}, Average Temp: {date[4]}")
        print("Weather data updated.")

    def box_plot(self):
        """
        This initiates and sends the variables for the box plot.
        """
        self.cursor.execute("SELECT sample_date FROM weather ORDER BY DATE(sample_date) ASC LIMIT 1")
        first_year = int(str(self.cursor.fetchone()[0])[0:4])
        self.cursor.execute("SELECT sample_date FROM weather ORDER BY DATE(sample_date) DESC LIMIT 1")
        last_year = int(str(self.cursor.fetchone()[0])[0:4])
        while True:
            try:
                start_year = int(input(f"Enter a start year between {first_year} and {last_year}: "))
                if first_year <= start_year <= last_year:
                    break
                else:
                    print(f"Please enter a year between {first_year} and {last_year}.")
            except ValueError:
                print("Error: Enter a valid integer.")

        while True:
            try:
                end_year = int(input(f"Enter a start year between {first_year} and {last_year}: "))
                if first_year <= end_year <= last_year:
                    break
                else:
                    print(f"Please enter a year between {first_year} and {last_year}.")
            except ValueError:
                print("Error: Enter a valid integer.")

        data = []
        current_year = start_year
        while current_year <= end_year:
            self.cursor.execute(f"SELECT * FROM weather where sample_date LIKE '{current_year}-%-%'")
            data.append(self.cursor.fetchall())
            current_year+=1
        plot = PlotOperations()
        plot.create_boxplot(data, start_year, end_year)

    def line_plot(self):
        """
        line plot selects and calls up a line plot.
        """
        self.cursor.execute("SELECT sample_date FROM weather ORDER BY DATE(sample_date) ASC LIMIT 1")
        first_year = int(str(self.cursor.fetchone()[0])[0:4])
        self.cursor.execute("SELECT sample_date FROM weather ORDER BY DATE(sample_date) DESC LIMIT 1")
        last_year = int(str(self.cursor.fetchone()[0])[0:4])
        while True:
            try:
                year = int(input(f"Enter a year between {first_year} and {last_year}: "))
                if first_year <= year <= last_year:
                    break
                else:
                    print(f"Please enter a year between {first_year} and {last_year}.")
            except ValueError:
                print("Error: Enter a valid integer.")

        while True:
            try:
                month = int(input(f"Enter a month between 1 and 12: "))
                if 1 <= month <= 12:
                    break
                else:
                    print(f"Please enter a month between 1 and 12.")
            except ValueError:
                print("Error: Enter a valid integer.")
        db = DBOperations("weather.db")
        query = f"SELECT * FROM weather WHERE sample_date LIKE '{year}-{month}-%'"
        self.cursor.execute(query)
        data_list = self.cursor.fetchall()
        if not data_list:
            print(f"No data available for {month}/{year}.")
            return
        plotter = PlotOperations()
        plotter.create_lineplot(data_list, month, year)

    def user_choice(self, choice):
        """
        This handles the users choice in the dialogue.
        """
        if choice == 1:
            self.full_pull()
        elif choice == 2:
            self.update_weather()
        elif choice == 3:
            self.box_plot()
        elif choice == 4:
            self.line_plot()
        elif choice == 5:
            self.conn.close()
            exit()
        else:
            print("Invalid selection, try again.")

    def process(self):
        """
        This initiates the weather menu when called.
        """

        self.weather_menu()
        try:
            user_selection = int(input("Enter an option(1-5): "))
            self.user_choice(user_selection)
        except ValueError:
            print("Invalid selection, try again.")

if __name__ == "__main__":
    weather_processor = WeatherProcessor()
    weather_processor.process()


# -*- coding: utf-8 -*-
import scrapy
from power_market.items import CurrentItem
from power_market.items import PdfItem

class EvnSpider(scrapy.Spider):
    name = 'EVN'
    allowed_domains = ['en.evn.com.vn']
    start_urls = ['https://en.evn.com.vn/c3/gioi-thieu-l/Annual-Report-6-13.aspx']
    base_url = "https://en.evn.com.vn"
    table_url = "https://en.evn.com.vn/c3/gioi-thieu-f/Projects-6-14.aspx"
    count = 1

    def parse(self, response):
        yield scrapy.Request(self.table_url,callback=self.table_grab) # 先抓取网页表格

        urls = response.xpath('//div[@class="blog-page page_list"]//@href').getall()
        urls = list(map(lambda x: response.urljoin(x),urls))

        for url in urls:
            if "Annual" in url:
                yield scrapy.Request(url,callback=self.pdf_grab) # 筛选含有pdf的链接并执行下载回调

    def pdf_grab(self,response):
        urls = response.xpath('//div[@id="ContentPlaceHolder1_ctl00_159_content_news"]//@href').getall()
        urls = list(map(lambda x: response.urljoin(x),urls))
        for pdf_url in urls:
            if ".pdf" in pdf_url: # 筛选还有.pdf的链接
                filename = str(pdf_url[-10:])
                item = PdfItem(filename=filename,pdf_url=pdf_url)
                self.count = self.count+1 # 下载计数+1
                yield item

    def table_grab(self,response):
        title = response.xpath('//span[@id="ContentPlaceHolder1_ctl00_1391_ltlTitle"]/text()').get() # 标题
        filename = "".join(title) + ".json"
        tables = response.xpath('//div[@class="blog margin-bottom-40 content-detail"]//tbody/tr') # 表格
        #keys = [] # 放到一个list序列中
        values = ''
        for table in tables:
            tds = table.xpath('td')
            for td in tds:
                values = values + "".join(td.xpath('p/text()').get()) + ','
            values = values + ';'
            #keys.append(tds[0].xpath('p/text()').get()) # 列键
            #values.append(tds[1].xpath('p/text()').get()) #列值
        item = CurrentItem(rename=filename,content=values)
        yield item


# This is a sample Python script.
import yfinance as yf
import pandas as pd
import numpy as np
import math as math

def get_stock_info(name):
    # Use a breakpoint in the code line below to debug your script.
    stock = yf.Ticker(name)
    return stock.history(period="1y")

def get_change(current, previous):
    if current == previous:
        return 0
    try:
        return (abs(current - previous) / previous) * 100.0
    except ZeroDivisionError:
        return float('inf')

# Press the green button in the gutter to run the script.
def calculate_dollar_cost_with_min_average_new(df_stock_info, amount, is_dividant):
    total_amount = 0
    total_shares = 0
    days_count = 0
    days_uninvested = 0
    days_not_invested = 0
    df_stock_info.index = np.arange(1, len(df) + 1)
    weekly_budget = amount * 5
    divident = 0
    total_dividant = 0
    count =1
    five_day_stock = 0
    five_day_amount = 0

    for index, row in df_stock_info.iterrows():
        closed = round(row['Close'],2)
        five_day_avg = row['5_day_avg']

        if row['Dividends'] != 0 and is_dividant:
            divident += (total_shares * row['Dividends'])
            total_dividant += divident
            share2 = divident / closed
            count+=1
            total_shares += share2

        if math.isnan(row['5_day_avg']):
            share = amount / closed
            total_amount += amount
            total_shares += share
            five_day_stock += share
            five_day_amount += amount
            days_count += 1
            weekly_budget -= amount
            days_uninvested = 0
            continue

        if index % 5 == 0:
            if weekly_budget > 0:
                share = (weekly_budget) / closed
                total_amount += (weekly_budget)
                total_shares += share
                days_count += (weekly_budget/amount)
                days_uninvested = 0
            five_day_stock = 0
            five_day_amount = 0
            weekly_budget = amount * 5

        our_five_day_avg = 0
        if five_day_stock != 0:
            our_five_day_avg = five_day_amount / five_day_stock

        if closed > (our_five_day_avg * 1.12):
            days_uninvested += 1
            days_not_invested+= 1
            continue
        elif weekly_budget > 0:
            share = amount / closed
            total_amount += amount
            total_shares += share

            five_day_stock += share
            five_day_amount += amount
            weekly_budget -= amount
            days_count += 1

            if closed < our_five_day_avg * 0.8 and weekly_budget > 0:
                share = (weekly_budget) / closed
                total_amount += (weekly_budget)
                total_shares += share
                days_count += (weekly_budget/amount)
                weekly_budget = 0
                days_uninvested = 0

    print("total_dividant", round(total_dividant, 2))
    print("total_amount", round(total_amount, 2))
    print("total_shares", round(total_shares, 2))
    print("days_count", days_count)
    print("days_not_invested", days_not_invested)
    print("Price per share ", total_amount / total_shares)
    print("==========================")

    return total_amount, total_shares, days_count

# Press the green button in the gutter to run the script.
def calculate_dollar_cost_with_min_average2(df_stock_info, amount):
    total_amount = 0
    total_shares = 0
    days_count = 0
    monthly_budget = amount * 20
    days_uninvested = 0

    df_stock_info.index = np.arange(1, len(df) + 1)

    for index, row in df_stock_info.iterrows():
        closed = row['Close']
        five_day_avg = row['5_day_avg']
        if index == 1:
            share = amount / closed
            total_amount += amount
            monthly_budget -= amount
            total_shares += share
            days_count+=1
            #print(row['Date'])
            continue
        if index % 20 == 0 and monthly_budget > 0:
            share = monthly_budget / closed
            total_amount += monthly_budget
            monthly_budget -= monthly_budget
            total_shares += share
            days_count += 1
            #print(row['Date'])
            continue
        if index % 20 == 0:
            monthly_budget = amount * 20

        if days_uninvested == 5:
             share = (amount * days_uninvested) / closed
             total_amount += (amount * days_uninvested)
             total_shares += share
             monthly_budget -= (amount * days_uninvested)
             days_uninvested = 0
             days_count += 5

        if math.isnan(row['5_day_avg']):
            share = amount / closed
            total_amount += amount
            monthly_budget -= amount
            total_shares += share
            days_count += 1
            continue
        if closed > (row['5_day_avg']):
            days_uninvested += 1
            continue
        if closed < (row['5_day_avg']) and monthly_budget > 0:
            if days_uninvested == 0:
                days_uninvested = 1

            share = (amount*days_uninvested) / closed
            total_amount += (amount*days_uninvested)
            total_shares += share
            monthly_budget-= (amount*days_uninvested)
            days_count += days_uninvested
            days_uninvested = 0

    print("total_amount", round(total_amount, 2))
    print("total_shares", round(total_shares, 2))
    print("days_count", days_count)
    print("Price per share ", (total_amount) / total_shares)
    print("==========================")

    return total_amount, total_shares, days_count

# Press the green button in the gutter to run the script.
def calculate_dollar_cost(df_stock_info, amount):
    total_amount = 0
    total_shares = 0
    days_count = 0
    total_dividant = 0
    divident = 0

    for index, row in df_stock_info.iterrows():
        closed = row['Close']
        share = amount / closed
        total_amount += amount
        total_shares += share
        if row['Dividends'] != 0:
            divident += (total_shares * row['Dividends'])
            total_dividant += divident

        days_count += 1

    print("total_dividant", round(total_dividant,2))
    print("total_amount", round(total_amount,2))
    print("total_shares", round(total_shares,2))
    print("days_count", days_count)
    print("Price per share ", total_amount/total_shares)
    print("==========================")
    return total_amount, total_shares, days_count

# Press the green button in the gutter to run the script.
def calculate_dollar_cost_with_min_average(df_stock_info, amount):
    total_amount = 0
    total_shares = 0
    days_count = 0
    total_dividant = 0
    divident = 0
    days_uninvested = 0
    monthly_budget = amount*20

    for index, row in df_stock_info.iterrows():
        closed = row['Close']
        if days_count == 0:
            share = amount / closed
            total_amount += amount
            monthly_budget -= amount
            total_shares += share
            days_uninvested +=1
        else:
            if days_uninvested == 20:
                share = (amount*20) / closed
                total_amount += amount*20
                monthly_budget -= (amount*20)
                total_shares += share
                days_uninvested = 1
                monthly_budget = amount * 20
            else:
                average_cost = total_amount/total_shares
                if closed < (average_cost * 1.02):
                    share = (amount*days_uninvested) / closed
                    total_amount += (amount*days_uninvested)
                    total_shares += share
                    days_uninvested = 1
                if closed <= average_cost:
                    share = amount / closed
                    total_amount += amount
                    monthly_budget -= amount
                    total_shares += share
                    days_uninvested = 1
                if closed > average_cost * 1.02:
                    days_uninvested += 1
        if days_count % 20 == 0:
            if monthly_budget > 0 :
                share = (monthly_budget) / closed
                total_amount += (monthly_budget)
                total_shares += share
                days_uninvested = 1
            monthly_budget = amount*20
        if row['Dividends'] != 0:
            divident += (total_shares * row['Dividends'])
            total_dividant += divident
        days_count += 1

    print("uninvested amount ", round(monthly_budget, 2))
    print("total_dividant", round(total_dividant,2))
    print("total_amount", round(total_amount,2))
    print("total_shares", round(total_shares,2))
    print("days_count", days_count)
    print("Price per share ", total_amount/total_shares)

    print("final total amount ", round(monthly_budget + total_amount, 2))
    print("==========================")
    return total_amount, total_shares, days_count

def calculate_consecutive_low(df_stock_info, amount, dailyInvest):
    price_2_days_back = 0
    price_1_days_back = 0
    amount_invested = 0
    total_shares = 0
    days_count = 0
    collective_amount = 0

    for index in range(len(df_stock_info)):

        closed = df_stock_info.loc[index, 'Open']
        if closed < price_1_days_back < price_2_days_back:
            days_count += 1
            share = (collective_amount + dailyInvest) / closed
            amount_invested += (collective_amount + dailyInvest)
            total_shares += share
            collective_amount = 0
            price_2_days_back = 0
            price_1_days_back = 0
        else:
            share = dailyInvest / closed
            collective_amount += (amount - dailyInvest)
            amount_invested += dailyInvest
            total_shares += share
            days_count += 1

        price_2_days_back = price_1_days_back
        price_1_days_back = closed

    print("total_amount for two low", amount_invested)
    print("total_shares for two low", total_shares)
    print("days_count for two low", days_count)
    print("Price per share ", amount_invested/total_shares )
    print("==========================")

    return amount_invested, total_shares, days_count

def divident_reinvestment(df_stock_info, amount):
    total_amount = 0
    total_shares = 0
    days_count = 0
    total_dividant = 0
    divident = 0
    for index, row in df_stock_info.iterrows():
        closed = row['Close']
        share = amount / closed
        total_amount += amount
        total_shares += share
        if row['Dividends'] != 0:
            divident += (total_shares * row['Dividends'])
            total_dividant += divident
            share2 = divident / closed
            #total_amount += divident
            total_shares += share2
        days_count += 1

    print("total_dividant", round(total_dividant,2))
    print("total_amount divident_reinvestment", round(total_amount,2))
    print("total_shares divident_reinvestment", round(total_shares,2))
    print("days_count divident_reinvestment", days_count)
    print("Price per share ", total_amount/total_shares )
    return total_amount, total_shares, days_count

def calculate_profit(total_amount_invested, total_shares, closed_price):
    profit = round(closed_price * total_shares - total_amount_invested,2)
    percent_change_amount = get_change(closed_price * total_shares, total_amount_invested)

    print("profit ", profit)
    print("percent_change_amount ", percent_change_amount)
    print("============== ")

    return profit, get_change(closed_price * total_shares, total_amount_invested )

def get_change(current, previous):
    if current == previous:
        return 0
    try:
        return (abs(current - previous) / previous) * 100.0
    except ZeroDivisionError:
        return float('inf')

Input_column = {
    "ID": int,
    "Name": str,
    "Address": str
}

if __name__ == '__main__':
    df_stock_info = get_stock_info('VTI')
    amount = 5
    #print(df_stock_info.columns)
    print(df_stock_info)

    # Convert the dictionary into DataFrame
    pd.set_option("display.max_rows", None, "display.max_columns", None)
    df = pd.DataFrame(df_stock_info, columns=['Open', 'High', 'Low', 'Close', 'Volume', 'Dividends', 'Stock Splits'])

    df['5_day_avg'] = round(df.Close.rolling(window=5).mean(), 2)
    df['closed_p_change'] = df.Close.pct_change(periods=1)

    max_value = df.closed_p_change.max()
    min_value = df.closed_p_change.min()
    mean_value = df.closed_p_change.mean()
    print(f"max value {max_value}")
    print(f"min value {min_value}")
    print(f"mean value {mean_value}")

    #df.to_csv("vti_10y.csv")

    print("last price")
    print(df['Close'].iloc[-1])

    #print("calculate_dollar_cost ")
    #total_amount_invested, total_shares, days_count = calculate_dollar_cost(df, amount)
    #profit, percent_change = calculate_profit(total_amount_invested, total_shares, df['Close'].iloc[-1])

    #print("calculate_dollar_cost_with_min_average ")
    #total_amount_invested, total_shares, days_count = calculate_dollar_cost_with_min_average_new(df, amount, True)
    #profit, percent_change = calculate_profit(total_amount_invested, total_shares, df['Close'].iloc[-1])

    print("============== ")
    print("divident_reinvestment")
    print("============== ")
    total_amount, total_shares, days_count = divident_reinvestment(df, amount)
    print("============== ")
    profit1, percent_change2 = calculate_profit(total_amount, total_shares, df['Close'].iloc[-1])

    #print("percent_change direct ", get_change(df['Close'].iloc[-1], df['Close'].iloc[0]))

    #print("percent_change ", percent_change)
    #print("percent_change ", percent_change2)
    #print("calculate_consecutive_low ")
    #total_amount_invested, total_shares, days_count = calculate_consecutive_low(df, 15, 0)

    #print("difference ", round(profit1 - profit, 2))


import queue
import shlex
import ssl
import subprocess
import sys
import time
from abc import ABC, abstractmethod
from shutil import copyfileobj
from threading import Thread
from typing import Tuple, Union
from urllib.error import URLError
from urllib.request import urlopen

from downloader.constants import K_DOWNLOADER_RETRIES, K_DOWNLOADER_SIZE_MB_LIMIT, \
    K_DOWNLOADER_PROCESS_LIMIT, K_DOWNLOADER_TIMEOUT, K_CURL_SSL, K_DEBUG, FILE_MiSTer_new, FILE_MiSTer, \
    FILE_MiSTer_old, K_DOWNLOADER_OLD_IMPLEMENTATION, K_DOWNLOADER_THREADS_LIMIT
from downloader.logger import DebugOnlyLoggerDecorator
from downloader.other import calculate_url
from downloader.target_path_repository import TargetPathRepository

class FileDownloaderFactory(ABC):
    @abstractmethod
    def create(self, config, parallel_update, silent=False, hash_check=True):
        """Created a Parallel or Serial File Downloader"""

def make_file_downloader_factory(file_system_factory, local_repository, waiter, logger):
    return _FileDownloaderFactoryImpl(file_system_factory, local_repository, waiter, logger)

class _FileDownloaderFactoryImpl(FileDownloaderFactory):
    def __init__(self, file_system_factory, local_repository, waiter, logger):
        self._file_system_factory = file_system_factory
        self._local_repository = local_repository
        self._waiter = waiter
        self._logger = logger

    def create(self, config, parallel_update, silent=False, hash_check=True):
        logger = DebugOnlyLoggerDecorator(self._logger) if silent else self._logger
        file_system = self._file_system_factory.create_for_config(config)
        target_path_repository = TargetPathRepository(config, file_system)
        if config[K_DOWNLOADER_OLD_IMPLEMENTATION]:
            self._logger.print('Using old downloader implementation...')
            if parallel_update:
                return _CurlCustomParallelDownloader(config, file_system, self._local_repository, logger, hash_check, target_path_repository)
            else:
                return _CurlSerialDownloader(config, file_system, self._local_repository, logger, hash_check, target_path_repository)
        else:
            thread_limit = config[K_DOWNLOADER_THREADS_LIMIT] if parallel_update else 1
            low_level_factory = _LowLevelMultiThreadingFileDownloaderFactory(thread_limit, config, self._waiter, logger)
            return HighLevelFileDownloader(hash_check, config, file_system, target_path_repository, low_level_factory, logger)

class FileDownloader(ABC):
    @abstractmethod
    def queue_file(self, file_description, file_path):
        """queues a file for downloading it later"""

    @abstractmethod
    def set_base_files_url(self, base_files_url):
        """sets the base_files_url from a database"""

    @abstractmethod
    def mark_unpacked_zip(self, zip_id, base_zips_url):
        """indicates that a zip is being used, useful for reporting"""

    @abstractmethod
    def download_files(self,  first_run):
        """download all the queued files"""

    @abstractmethod
    def errors(self):
        """all files with errors"""

    @abstractmethod
    def correctly_downloaded_files(self):
        """all correctly downloaded files"""

    @abstractmethod
    def needs_reboot(self):
        """returns true if a file that needs reboot has been downloaded"""

class LowLevelFileDownloader(ABC):
    def fetch(self, files_to_download, paths):
        """"files_to_download is a dictionary with file_path as keys and file_description as values"""

    def network_errors(self):
        """returns errors that happened during download_files"""

    def downloaded_files(self):
        """returns files downloaded during download_files"""

class LowLevelFileDownloaderFactory(ABC):
    def create_low_level_file_downloader(self, high_level):
        """"returns instance of LowLevelFileDownloader"""

class DownloadValidator(ABC):
    def validate_download(self, file_path: str, file_hash: str) -> Tuple[int, Union[str, Tuple[str, str]]]:
        """Validates that the downloaded file is correctly installed and moves it if necessary. Returned int is 1 if validation was correct."""

class HighLevelFileDownloader(FileDownloader, DownloadValidator):
    def __init__(self, hash_check, config, file_system, target_path_repository, low_level_file_downloader_factory, logger):
        self._hash_check = hash_check
        self._file_system = file_system
        self._target_path_repository = target_path_repository
        self._config = config
        self._low_level_file_downloader_factory = low_level_file_downloader_factory
        self._logger = logger
        self._run_files = []
        self._queued_files = {}
        self._base_files_url = None
        self._unpacked_zips = {}
        self._needs_reboot = False
        self._errors = []
        self._correct_files = []

    def queue_file(self, file_description, file_path):
        self._queued_files[file_path] = file_description

    def set_base_files_url(self, base_files_url):
        self._base_files_url = base_files_url

    def mark_unpacked_zip(self, zip_id, base_zips_url):
        self._unpacked_zips[zip_id] = base_zips_url

    def download_files(self, _):
        for _ in range(self._config[K_DOWNLOADER_RETRIES] + 1):
            self._errors = self._download_try()
            if len(self._errors):
                continue

            break

        if self._file_system.is_file(FILE_MiSTer_new):
            self._logger.print('')
            self._logger.print('Copying new MiSTer binary:')
            if self._file_system.is_file(FILE_MiSTer):
                self._file_system.move(FILE_MiSTer, FILE_MiSTer_old)
            self._file_system.move(FILE_MiSTer_new, FILE_MiSTer)

            if self._file_system.is_file(FILE_MiSTer):
                self._logger.print('New MiSTer binary copied.')
            else:
                # This error message should never happen.
                # If it happens it would be an unexpected case where file_system is not moving files correctly
                self._logger.print('CRITICAL ERROR!!! Could not restore the MiSTer binary!')
                self._logger.print('Please manually rename the file MiSTer.new as MiSTer')
                self._logger.print('Your system won\'nt be able to boot until you do so!')
                sys.exit(1)

    def _download_try(self):
        if len(self._queued_files) == 0:
            self._logger.print("Nothing new to download from given sources.")
            return []

        self._logger.print("Downloading %d files:" % len(self._queued_files))

        low_level = self._low_level_file_downloader_factory.create_low_level_file_downloader(self)

        files_to_download = []
        skip_files = []
        for file_path, file_description in self._queued_files.items():
            if self._hash_check and self._file_system.is_file(file_path):
                path_hash = self._file_system.hash(file_path)
                if path_hash == file_description['hash']:
                    if 'zip_id' in file_description and file_description['zip_id'] in self._unpacked_zips:
                        self._logger.print('Unpacked: %s' % file_path)
                    else:
                        self._logger.print('No changes: %s' % file_path)  # @TODO This scenario might be redundant now, since it's also checked in the Online Importer
                    skip_files.append(file_path)
                    continue
                else:
                    self._logger.debug('%s: %s != %s' % (file_path, file_description['hash'], path_hash))

            if 'url' not in file_description:
                file_description['url'] = calculate_url(self._base_files_url, file_path)

            self._file_system.make_dirs_parent(file_path)
            target_path = self._target_path_repository.create_target(file_path, file_description)
            files_to_download.append((file_path, self._file_system.download_target_path(target_path)))
            self._run_files.append(file_path)

        for file_path in skip_files:
            self._correct_files.append(file_path)
            self._queued_files.pop(file_path)

        low_level.fetch(files_to_download, self._queued_files)

        self._check_downloaded_files(low_level.downloaded_files())

        return low_level.network_errors()

    def validate_download(self, file_path: str, file_hash: str) -> Tuple[int, Union[str, Tuple[str, str]]]:
        target_path = self._target_path_repository.access_target(file_path)
        if not self._file_system.is_file(target_path):
            return 2, (file_path, 'Missing %s' % file_path)

        path_hash = self._file_system.hash(target_path)
        if self._hash_check and path_hash != file_hash:
            self._target_path_repository.clean_target(file_path)
            return 2, (file_path, 'Bad hash on %s (%s != %s)' % (file_path, file_hash, path_hash))

        self._target_path_repository.finish_target(file_path)
        self._logger.debug('+', end='', flush=True)

        return 1, file_path

    def _check_downloaded_files(self, files):
        for path in files:
            self._correct_files.append(path)
            if self._queued_files[path].get('reboot', False):
                self._needs_reboot = True

            self._queued_files.pop(path)

    def errors(self):
        return self._errors

    def correctly_downloaded_files(self):
        return self._correct_files

    def needs_reboot(self):
        return self._needs_reboot

    def run_files(self):
        return self._run_files

def context_from_curl_ssl(curl_ssl):
    context = ssl.create_default_context()

    if curl_ssl.startswith('--cacert '):
        cacert_file = curl_ssl[len('--cacert '):]
        context.load_verify_locations(cacert_file)
    elif curl_ssl == '--insecure':
        context.check_hostname = False
        context.verify_mode = ssl.CERT_NONE

    return context

class _LowLevelMultiThreadingFileDownloaderFactory(LowLevelFileDownloaderFactory):
    def __init__(self, threads_limit, config, waiter, logger):
        self._threads_limit = threads_limit
        self._config = config
        self._waiter = waiter
        self._logger = logger

    def create_low_level_file_downloader(self, download_validator):
        return _LowLevelMultiThreadingFileDownloader(self._threads_limit, self._config, context_from_curl_ssl(self._config[K_CURL_SSL]), self._waiter, self._logger, download_validator)

class _LowLevelMultiThreadingFileDownloader(LowLevelFileDownloader):
    def __init__(self, threads_limit, config, context, waiter, logger, download_validator):
        self._threads_limit = threads_limit
        self._config = config
        self._context = context
        self._waiter = waiter
        self._logger = logger
        self._download_validator = download_validator
        self._network_errors = _DownloadErrors(self._logger)
        self._downloaded_files = []
        self._pending_notifications = []
        self._endl_pending = False

    def fetch(self, files_to_download, descriptions):
        job_queue = queue.Queue()
        notify_queue = queue.Queue()
        for path, target in files_to_download:
            job_queue.put((descriptions[path]['url'], path, target), False)

        threads = [Thread(target=self._thread_worker, args=(job_queue, notify_queue)) for _ in range(min(self._threads_limit, len(files_to_download)))]
        for thread in threads:
            thread.start()

        remaining_notifications = len(files_to_download) * 2

        while remaining_notifications > 0:
            remaining_notifications -= self._read_notifications(descriptions, notify_queue, True)
            self._waiter.sleep(1)

        job_queue.join()

        for thread in threads:
            thread.join()

        self._read_notifications(descriptions, notify_queue, False)
        self._logger.print()

    def _read_notifications(self, descriptions, notify_queue, in_progress):
        new_files = False
        read_notifications = 0
        while not notify_queue.empty():
            state, path = notify_queue.get(False)
            notify_queue.task_done()

            if state == 0:
                if self._endl_pending:
                    self._endl_pending = False
                    self._logger.print()
                self._logger.print(path, flush=True)

                new_files = True
            elif state == 1:
                self._pending_notifications.append(self._download_validator.validate_download(path, descriptions[path]['hash']))
            else:
                self._pending_notifications.append((state, path))

            read_notifications += 1

        if new_files:
            return read_notifications

        if len(self._pending_notifications) > 0:
            for state, pack in self._pending_notifications:
                if state == 1:
                    path = pack
                    self._downloaded_files.append(path)
                    self._logger.print('.', end='', flush=True)
                else:
                    path, message = pack
                    self._network_errors.add_debug_report(path, message)
                    self._logger.print('~', end='', flush=True)

        elif in_progress:
            self._logger.print('*', end='', flush=True)

        self._endl_pending = in_progress
        self._pending_notifications.clear()
        return read_notifications

    def network_errors(self):
        return self._network_errors.list()

    def downloaded_files(self):
        return self._downloaded_files

    def _thread_worker(self, job_queue, notify_queue):
        while not job_queue.empty():
            url, path, target = job_queue.get(False)

            notify_queue.put((0, path), False)
            try:
                with urlopen(url, timeout=self._config[K_DOWNLOADER_TIMEOUT], context=self._context) as in_stream, open(target, 'wb') as out_file:
                    if in_stream.status == 200:
                        copyfileobj(in_stream, out_file)
                        notify_queue.put((1, path), False)
                    else:
                        notify_queue.put((2, (path, 'Bad http status! %s: %s' % (path, in_stream.status))), False)

            except URLError as e:
                notify_queue.put((2, (path, 'HTTP error! %s: %s' % (path, e.reason))), False)
            except ConnectionResetError as e:
                notify_queue.put((2, (path, 'Connection reset error! %s: %s' % (path, str(e)))), False)
            except Exception as e:
                notify_queue.put((2, (path, 'Exception during download! %s: %s' % (path, str(e)))), False)

            job_queue.task_done()

class CurlDownloaderAbstract(FileDownloader):
    def __init__(self, config, file_system, local_repository, logger, hash_check, temp_files_registry):
        self._config = config
        self._file_system = file_system
        self._logger = logger
        self._local_repository = local_repository
        self._hash_check = hash_check
        self._temp_files_registry = temp_files_registry
        self._curl_list = {}
        self._errors = _DownloadErrors(logger)
        self._http_oks = _HttpOks()
        self._correct_downloads = []
        self._needs_reboot = False
        self._base_files_url = None
        self._unpacked_zips = dict()

    def queue_file(self, file_description, file_path):
        self._curl_list[file_path] = file_description

    def set_base_files_url(self, base_files_url):
        self._base_files_url = base_files_url

    def mark_unpacked_zip(self, zip_id, base_zips_url):
        self._unpacked_zips[zip_id] = base_zips_url

    def download_files(self, first_run):
        self._download_files_internal(first_run)

        if self._file_system.is_file(FILE_MiSTer_new):
            self._logger.print()
            self._logger.print('Copying new MiSTer binary:')
            if self._file_system.is_file(FILE_MiSTer):
                self._file_system.move(FILE_MiSTer, FILE_MiSTer_old)
            self._file_system.move(FILE_MiSTer_new, FILE_MiSTer)

            if self._file_system.is_file(FILE_MiSTer):
                self._logger.print('New MiSTer binary copied.')
            else:
                # This error message should never happen.
                # If it happens it would be an unexpected case where file_system is not moving files correctly
                self._logger.print('CRITICAL ERROR!!! Could not restore the MiSTer binary!')
                self._logger.print('Please manually rename the file MiSTer.new as MiSTer')
                self._logger.print('Your system won\'nt be able to boot until you do so!')
                sys.exit(1)

    def _download_files_internal(self, first_run):
        if len(self._curl_list) == 0:
            self._logger.print("Nothing new to download from given sources.")
            return

        self._logger.print("Downloading %d files:" % len(self._curl_list))

        for path in sorted(self._curl_list):
            description = self._curl_list[path]
            if self._hash_check and self._file_system.is_file(path):
                path_hash = self._file_system.hash(path)
                if path_hash == description['hash']:
                    if 'zip_id' in description and description['zip_id'] in self._unpacked_zips:
                        self._logger.print('Unpacked: %s' % path)
                    else:
                        self._logger.print('No changes: %s' % path)  # @TODO This scenario might be redundant now, since it's also checked in the Online Importer
                    self._correct_downloads.append(path)
                    continue
                else:
                    self._logger.debug('%s: %s != %s' % (path, description['hash'], path_hash))

            if first_run:
                if 'delete' in description:
                    for _ in description['delete']:  # @TODO This is Deprecated
                        self._file_system.delete_previous(path)
                        break
                elif 'delete_previous' in description and description['delete_previous']:
                    self._file_system.delete_previous(path)

            self._download(path, description)

        self._wait()
        self._check_hashes()

        for retry in range(self._config[K_DOWNLOADER_RETRIES]):

            if self._errors.none():
                return

            for path in self._errors.consume():
                self._download(path, self._curl_list[path])

            self._wait()
            self._check_hashes()

    def _check_hashes(self):
        if self._http_oks.none():
            return

        self._logger.print()
        self._logger.print('Checking hashes...')

        for path in self._http_oks.consume():
            if not self._file_system.is_file(self._temp_files_registry.access_target(path)):
                self._errors.add_debug_report(path, 'Missing %s' % path)
                continue

            path_hash = self._file_system.hash(self._temp_files_registry.access_target(path))
            if self._hash_check and path_hash != self._curl_list[path]['hash']:
                self._errors.add_debug_report(path, 'Bad hash on %s (%s != %s)' % (path, self._curl_list[path]['hash'], path_hash))
                self._temp_files_registry.clean_target(path)
                continue

            self._temp_files_registry.finish_target(path)
            self._logger.print('+', end='', flush=True)
            self._correct_downloads.append(path)
            if self._curl_list[path].get('reboot', False):
                self._needs_reboot = True

        self._logger.print()

    def _download(self, path, description):
        if 'zip_path' in description:
            raise FileDownloaderError('zip_path is not a valid field for the file "%s", please contain the DB maintainer' % path)

        self._logger.print(path)
        self._file_system.make_dirs_parent(path)

        if 'url' not in description:
            description['url'] = calculate_url(self._base_files_url, path)

        target_path = self._temp_files_registry.create_target(path, description)

        if self._config[K_DEBUG] and target_path.startswith('/tmp/') and not description['url'].startswith('http'):
            self._file_system.copy(description['url'], target_path)
            self._run(description, 'echo > /dev/null', path)
            return

        self._run(description, self._command(target_path, description['url']), path)

    def _command(self, target_path, url):
        return 'curl %s --show-error --fail --location -o "%s" "%s"' % (self._config[K_CURL_SSL], target_path, url)

    def errors(self):
        return self._errors.list()

    def correctly_downloaded_files(self):
        return self._correct_downloads

    def needs_reboot(self):
        return self._needs_reboot

    @abstractmethod
    def _wait(self):
        """"waits until all downloads are completed"""

    @abstractmethod
    def _run(self, description, command, path):
        """"starts the downloading process"""

class _CurlCustomParallelDownloader(CurlDownloaderAbstract):
    def __init__(self, config, file_system, local_repository, logger, hash_check, temp_file_registry):
        super().__init__(config, file_system, local_repository, logger, hash_check, temp_file_registry)
        self._processes = []
        self._files = []
        self._acc_size = 0

    def _run(self, description, command, file):
        self._acc_size = self._acc_size + description['size']

        result = subprocess.Popen(shlex.split(command), shell=False, stderr=subprocess.DEVNULL,
                                  stdout=subprocess.DEVNULL)

        self._processes.append(result)
        self._files.append(file)

        more_accumulated_size_than_limit = self._acc_size > (1000 * 1000 * self._config[K_DOWNLOADER_SIZE_MB_LIMIT])
        more_processes_than_limit = len(self._processes) > self._config[K_DOWNLOADER_PROCESS_LIMIT]

        if more_accumulated_size_than_limit or more_processes_than_limit:
            self._wait()

    def _wait(self):
        count = 0
        start = time.time()
        while count < len(self._processes):
            some_completed = False
            for i, p in enumerate(self._processes):
                if p is None:
                    continue
                result = p.poll()
                if result is not None:
                    self._processes[i] = None
                    some_completed = True
                    count = count + 1
                    start = time.time()
                    self._logger.print('.', end='', flush=True)
                    if result == 0:
                        self._http_oks.add(self._files[i])
                    else:
                        self._errors.add_debug_report(self._files[i], 'Bad http code! %s: %s' % (result, self._files[i]))
            end = time.time()
            if (end - start) > self._config[K_DOWNLOADER_TIMEOUT]:
                for i, p in enumerate(self._processes):
                    if p is None:
                        continue
                    self._errors.add_debug_report(self._files[i], 'Timeout! %s' % self._files[i])
                break

            time.sleep(1)
            if not some_completed:
                self._logger.print('*', end='', flush=True)

        self._logger.print(flush=True)
        self._processes = []
        self._files = []
        self._acc_size = 0

class _CurlSerialDownloader(CurlDownloaderAbstract):
    def __init__(self, config, file_system, local_repository, logger, hash_check, temp_file_registry):
        super().__init__(config, file_system, local_repository, logger, hash_check, temp_file_registry)

    def _run(self, description, command, file):
        result = subprocess.run(shlex.split(command), shell=False, stderr=subprocess.STDOUT)
        if result.returncode == 0:
            self._http_oks.add(file)
        else:
            self._errors.add_print_report(file, 'Bad http code! %s: %s' % (result.returncode, file))

        self._logger.print()

    def _wait(self):
        pass

class _DownloadErrors:
    def __init__(self, logger):
        self._logger = logger
        self._errors = []

    def add_debug_report(self, path, message):
        self._logger.print('~', end='', flush=True)
        self._logger.debug(message, flush=True)
        self._errors.append(path)

    def add_print_report(self, path, message):
        self._logger.print(message, flush=True)
        self._errors.append(path)

    def none(self):
        return len(self._errors) == 0

    def consume(self):
        errors = self._errors
        self._errors = []
        return errors

    def list(self):
        return self._errors

class _HttpOks:
    def __init__(self):
        self._oks = []

    def add(self, path):
        self._oks.append(path)

    def consume(self):
        oks = self._oks
        self._oks = []
        return oks

    def none(self):
        return len(self._oks) == 0

class FileDownloaderError(Exception):
    pass


import numpy as np
import sklearn.metrics
import matplotlib.pyplot as plt
import tqdm

def prcurve_from_similarity_matrix(GT_MAP, SIM_MAP, nintervals, visualize = False):
    GTP = np.sum(GT_MAP)
    alt_GTP = np.sum(np.sum(GT_MAP,axis=1) > 0)
    precisions = []
    recalls = []
    alt_precisions = []
    alt_recalls = []
    #min_dist = np.min(SIM_MAP)
    max_sim = np.max(SIM_MAP)
    min_sim = np.min(SIM_MAP)
    start = max_sim#2*min_dist
    step = (max_sim - min_sim) / nintervals#(1+start) / nintervals#(2-start)/nintervals

    def get_threshold(i):
        return start - step * (i+1)

    prev_alt_TP = np.zeros((GT_MAP.shape[0]))

    print("Generating PR curve ...")
    for i in tqdm.tqdm(range(nintervals+1)):
        threshold = get_threshold(i)
        MASK = SIM_MAP >= threshold
        TP = np.sum(GT_MAP * MASK)
        FP = np.sum((1-GT_MAP)*MASK)
        precision = TP/(TP+FP)
        recall = TP/GTP
        precisions.append(precision)
        recalls.append(recall)
        alt_FP = np.logical_and(np.sum((1-GT_MAP)*MASK,axis=1) > 0, np.logical_not(prev_alt_TP)) # nqueryx1, rows with at least one false positive
        alt_TP = np.logical_and(np.sum(GT_MAP * MASK,axis = 1) > 0,np.logical_not(alt_FP)) # nqueryx1, rows with at least one true positive and no false positives
        prev_alt_TP = np.logical_or(prev_alt_TP,alt_TP)
        alt_TP = np.sum(alt_TP)
        alt_FP = np.sum(alt_FP)
        alt_precision = alt_TP/(alt_TP+alt_FP)
        alt_recall = alt_TP/alt_GTP
        alt_precisions.append(alt_precision)
        alt_recalls.append(alt_recall)
    print("Done")
    nintervals = nintervals + 1

    recalls = np.array(recalls)
    precisions = np.array(precisions)
    auc = sklearn.metrics.auc(recalls, precisions)
    print("Area under curve: ",auc)

    alt_recalls = np.array(alt_recalls)
    alt_precisions = np.array(alt_precisions)
    alt_auc = sklearn.metrics.auc(alt_recalls, alt_precisions)
    print("Area under curve (alt): ",alt_auc)

    f1_scores = 2*(precisions * recalls)/(precisions+recalls+1e-6)
    optimal_threshold_index = np.argmax(f1_scores)
    optimal_threshold = get_threshold(optimal_threshold_index)
    print("optimal threshold: ",optimal_threshold)
    print("Precision at optimal threshold: ", precisions[optimal_threshold_index])
    print("Recall at optimal threshold: ", recalls[optimal_threshold_index])

    alt_f1_scores = 2*(alt_precisions * alt_recalls)/(alt_precisions+alt_recalls+1e-6)
    alt_optimal_threshold_index = np.argmax(alt_f1_scores)
    alt_optimal_threshold = get_threshold(alt_optimal_threshold_index)
    print("optimal threshold (alt): ",alt_optimal_threshold)
    print("Precision at optimal threshold (alt): ", alt_precisions[alt_optimal_threshold_index])
    print("Recall at optimal threshold (alt): ", alt_recalls[alt_optimal_threshold_index])

    max_recall_100 = 0
    max_recall_99 = 0
    max_recall_95 = 0

    for p,r in zip(precisions, recalls):
        if p >= 0.95:
            max_recall_95 = max(max_recall_95, r)
            if p >= 0.99:
                max_recall_99 = max(max_recall_99, r)
                if p >= 1.:
                    max_recall_100 = max(max_recall_100, r)

    print("R@100P:", max_recall_100)
    print("R@99P:", max_recall_99)
    print("R@95P:", max_recall_95)

    max_recall_100 = 0
    max_recall_99 = 0
    max_recall_95 = 0

    for p,r in zip(alt_precisions, alt_recalls):
        if p >= 0.95:
            max_recall_95 = max(max_recall_95, r)
            if p >= 0.99:
                max_recall_99 = max(max_recall_99, r)
                if p >= 1.:
                    max_recall_100 = max(max_recall_100, r)

    print("R@100P (alt):", max_recall_100)
    print("R@99P (alt):", max_recall_99)
    print("R@95P (alt):", max_recall_95)

    if visualize:
        _, axs = plt.subplots(1,2)
        axs[0].plot(recalls, precisions , marker='o')
        axs[0].axvline(recalls[optimal_threshold_index], color='r')
        axs[0].set(xlabel="recall", ylabel="precision")
        axs[0].title.set_text("PR Curve")
        axs[1].plot(list(range(nintervals)), precisions, color = 'b')
        axs[1].plot(list(range(nintervals)), recalls, color = 'g')
        axs[1].plot(list(range(nintervals)), f1_scores, color = 'k')
        axs[1].axvline(optimal_threshold_index, color='r')
        axs[1].set(xlabel="threshold", ylabel="precision - recall")
        axs[1].title.set_text("Precision (b) and recall (g) curves")
        plt.show()

        _, axs = plt.subplots(1,2)
        axs[0].plot(alt_recalls, alt_precisions , marker='o')
        axs[0].axvline(alt_recalls[alt_optimal_threshold_index], color='r')
        axs[0].set(xlabel="recall", ylabel="precision")
        axs[0].title.set_text("PR Curve")
        axs[1].plot(list(range(nintervals)), alt_precisions, color = 'b')
        axs[1].plot(list(range(nintervals)), alt_recalls, color = 'g')
        axs[1].plot(list(range(nintervals)), alt_f1_scores, color = 'k')
        axs[1].axvline(alt_optimal_threshold_index, color='r')
        axs[1].set(xlabel="threshold", ylabel="precision - recall")
        axs[1].title.set_text("Precision (b) and recall (g) curves")
        plt.show()

    return alt_precisions[alt_optimal_threshold_index], alt_auc, alt_optimal_threshold, [get_threshold(i) for i in range(nintervals)]


import SuperClass as sc
import tkinter as tk
from PIL import Image,ImageTk

class Entity(sc.SpaceInvader):
    def __init__(self):
        sc.SpaceInvader.__init__(self)
        self.canvas.bind("<Configure>", self.update)

        self.vie = 1
        self.sprite = Image.open("media/image/male_sprite_model.png")
        self.anim = [[2,3,4,3,2,1,0,1], [2,3,4,3,2,1,0,1], [1,2,3,5,6,7], [1,2,3,5,6,7]]
        self.direction = 1
        self.animation = 0
        self.cropingSrpite() #definie self.cropSprite
        self.position = (10, 10)
        self.canvas.after(self.tac, self.tic)

    def cropingSrpite(self, direction = None, animation = None):#haut direction 0,   bas direction 1,   droite direction 2,   gauche direction 3
        if direction == None:
            direction = self.direction
        else:
            self.direction = direction

        if animation == None:
            animation = self.animation
        else:
            self.animation = animation
        self.cropSprite = self.sprite.crop((32*self.anim[direction][animation], 64*direction, 32*(self.anim[direction][animation]+1), 64*(direction+1)))
        self.update()

    def update(self, e=None):
        #self.canvas.delete("all")
        """
        redimentionne l'image
        """
        #resize
        self.resizeSprite= self.cropSprite.resize((self.vw(10)+1,2*self.vw(10)+1), Image.ANTIALIAS)#+1 car on ne doit pas resize par 0
        self.tkResizeSprite = ImageTk.PhotoImage(self.resizeSprite)
        self.canvas.create_image(self.vw(50),self.vh(50), image = self.tkResizeSprite)

    def tic(self):
        if self.animation+1 < len(self.anim[self.direction]):
            self.animation += 1
        else:
            self.animation = 0
        self.canvas.after(self.tac, self.tic)
        self.cropingSrpite()

class Joueur(Entity):
    def __init__(self):
        Entity.__init__(self)
        self.canvas.bind_all("<Key>", self.moving)

    def moving(self, e):
        if e.char == "z":
            self.cropingSrpite(0,0)
        elif e.char == "s":
            self.cropingSrpite(1,0)
        elif e.char == "d":
            self.cropingSrpite(2,0)
        elif e.char == "q":
            self.cropingSrpite(3,0)
        else:
            print(e.char)


# This code is based on the following example:
# https://discordpy.readthedocs.io/en/stable/quickstart.html#a-minimal-bot

import discord
from discord.ext import commands
import os
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

client_id = os.getenv('SPOTIPY_CLIENT_ID')
client_secret = os.getenv('SPOTIPY_CLIENT_SECRET')
bot_token = os.getenv('BOT_TOKEN')

intents = discord.Intents.default()  # Create an instance of Intents
intents.typing = False  # Disable typing events, if desired
intents.presences = False  # Disable presence events, if desired

bot = commands.Bot(command_prefix='/', intents=intents)
client = discord.Client(intents=intents)

logic_uri = 'https://open.spotify.com/artist/4xRYI6VqpkE3UwrDrAZL8L?si=sqOCFbjFRx6jpHaWogSVrg'

client_credentials_manager = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

results = sp.artist_albums(logic_uri, album_type='album')
albums = results['items']
while results['next']:
    results = sp.next(results)
    albums.extend(results['items'])

for album in albums:
    print(album['name'])

id_list = []

tracks = sp.album_tracks('https://open.spotify.com/album/7viNUmZZ8ztn2UB4XB3jIL?si=CoKXRho2SOuD5FByREFw9A')

# if 'items' in tracks:
#     for track in tracks['items']:
#         id_list.append(tracks['id'])
# else:
#     print("No tracks found for the given album ID.")
# print(tracks['uri'])

# @bot.event
# async def on_ready():
#     print(f'Bot connected as {bot.user.name}')

sp.start_playback(device_id=None, uris= ['spotify:artist:6l3HvQ5sa6mXTsMTB19rO5'], offset=None)

# client.run(os.getenv(bot_token))


from enum import Enum

class InsertType(Enum):
    """Type of inserted in google suite."""

    TEXT = "text"
    TABLE = "table"
    GRAPH = "graph"
    IMAGE = "image"

class ScopeType(Enum):
    """Type of scopes for google suite.

    PRESENTATION_EDITABLE: Allows read/write access to the user's presentations and their properties.
    PRESENTATION_READONLY: Allows read-only access to the user's presentations and their properties.
    SHEETS_EDITABLE: Allows read/write access to the user's sheets and their properties.
    SHEETS_READONLY: Allows read-only access to the user's sheets and their properties.
    DRIVE_FILES: Per-file access to files created or opened by the app.
    DRIVE_READONLY: Allows read-only access to the user's file metadata and file content.
    DRIVE: Full, permissive scope to access all of a user's files.
        Request this scope only when it is strictly necessary.
    """

    PRESENTATION_EDITABLE = ["https://www.googleapis.com/auth/presentations"]
    PRESENTATION_READONLY = ["https://www.googleapis.com/auth/presentations.readonly"]
    SHEETS_EDITABLE = ["https://www.googleapis.com/auth/spreadsheets"]
    SHEETS_READONLY = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
    DRIVE_FILES = ["https://www.googleapis.com/auth/drive.file"]
    DRIVE_READONLY = ["https://www.googleapis.com/auth/drive.readonly"]
    DRIVE = ["https://www.googleapis.com/auth/drive"]

class CredentialType(Enum):
    """Type of credential."""

    API_KEYS = "api_keys"
    OAUTH = "oauth"
    SERVICE_ACCOUNT = "service_account"

class SlideLayout(Enum):
    """Type of slide layout"""

    BLANK = "BLANK"
    CAPTION_ONLY = "CAPTION_ONLY"
    TITLE = "TITLE"
    TITLE_AND_BODY = "TITLE_AND_BODY"
    TITLE_AND_TWO_COLUMNS = "TITLE_AND_TWO_COLUMNS"
    TITLE_ONLY = "TITLE_ONLY"
    SECTION_HEADER = "SECTION_HEADER"
    SECTION_TITLE_AND_DESCRIPTION = "SECTION_TITLE_AND_DESCRIPTION"
    ONE_COLUMN_TEXT = "ONE_COLUMN_TEXT"
    MAIN_POINT = "MAIN_POINT"
    BIG_NUMBER = "BIG_NUMBER"


import os
import pandas as pd
import numpy as np
import pickle
import json
from uuid import uuid4
from time import time
from importlib import import_module
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer

from params import Params

# input files
train_feat_path = os.path.join(os.getenv('DATA_PATH'), os.getenv('TRAIN_FEATURES_FILENAME'))
train_labels_path = os.path.join(os.getenv('DATA_PATH'), os.getenv('TRAIN_LABELS_FILENAME'))

# output files
model_path = os.path.join(os.getenv('MODEL_PATH'), os.getenv('MODEL_FILENAME'))
best_params_path = os.path.join(os.getenv('MODEL_PATH'), os.getenv('BEST_PARAMS_FILENAME'))
metrics_path = os.path.join(os.getenv('MODEL_PATH'), os.getenv('METRICS_FILENAME'))

def load_data():
    """
    Loads train set
    :return: X_train, y_train
    """

    print("Loading dataset...")
    X_train = pd.read_csv(train_feat_path, low_memory=True).set_index('form_id')
    y_train = pd.read_csv(train_labels_path, low_memory=True).set_index('form_id')

    return X_train, y_train

def load_pipeline():
    """
    Loads training pipeline
    :return: Pipeline, scorer
    """

    Scaler = getattr(
        import_module(
            Params.SCALER_ORIGIN
        ),
        Params.SCALER_NAME
    )

    Model = getattr(
        import_module(
            Params.ALGO_ORIGIN
        ),
        Params.ALGO_NAME
    )

    scorer = make_scorer(
        getattr(
            import_module(
                Params.METRIC_ORIGIN
            ),
            Params.METRIC_NAME
        )
    )

    return Pipeline([('scaler', Scaler()), ('model', Model())]), scorer

if __name__ == "__main__":

    time_init = time()
    print("Starting training stage...")

    X_train, y_train = load_data()
    n_samples, n_features = X_train.shape

    print("Train set:")
    print(f"-> {n_samples} samples")
    print(f"-> {n_features} features")

    pipeline, scorer = load_pipeline()
    model = GridSearchCV(pipeline,
                         param_grid=eval(Params.PARAMS_GRID),
                         cv=Params.CV,
                         scoring=scorer,
                         verbose=1)

    print("Performing grid search...")
    print("Pipeline:", [name for name, _ in pipeline.steps])
    print("Parameters:")
    print(eval(Params.PARAMS_GRID))
    t0 = time()

    model.fit(X_train, y_train.values.ravel())
    print(f"Done in {round((time() - t0), 3)} seconds")
    print()

    model_id = str(uuid4())
    best_parameters = model.best_estimator_.get_params()
    best_score = model.best_score_

    print(f"Trained model ID: {model_id}")
    print(f"Best score on {Params.METRIC_NAME}: {best_score}")
    print("Best parameters set:")
    best_params_dict = {"model_id": model_id}
    for param_name in sorted(best_parameters.keys()):
        if '__' in param_name:
            best_params_dict[param_name.split('__')[1]] = best_parameters[param_name]
            print("\t%s: %r" % (param_name.split('__')[1], best_parameters[param_name]))

    with open(model_path, 'wb') as outfile:
        versioned_model = {
            "model": model,
            "model_id": model_id,
            "feats_idx": Params.RELEVANT_FEATS_IDX}
        pickle.dump(versioned_model, outfile)

    with open(best_params_path, 'w') as outfile:
        json.dump(best_params_dict, outfile)

    metrics = {
        "model_id": model_id,
        "train": {
            Params.METRIC_NAME: best_score,
            "support": X_train.shape[0],
        }
    }

    with open(metrics_path, 'w') as outfile:
        json.dump(metrics, outfile)

    print(f"Training stage finished in {round(time() - time_init, 2)} seconds")


import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def calculate_hit_rate(left_out_dict, user_ids_lst, top_20_recommended_ids):
    '''
    Claculate hit rate for top 20 using left-one-out set
    '''
    hit_rate = 0
    total_users = len(user_ids_lst)

    for user, ids_lst in zip(user_ids_lst, top_20_recommended_ids):
        if left_out_dict[user] in ids_lst:
            hit_rate += 1
    return hit_rate/total_users

def aggregare_vectors(people_lst, artists_vectors_df):
    '''
    return mean of the vectors from people_lst
    '''
    vector_matrix = np.array(artists_vectors_df[artists_vectors_df['person_id'].isin(set(people_lst))]['vector'])
    if len(vector_matrix) > 1:
        return np.mean(vector_matrix, axis=0)
    else:
        if len(vector_matrix) == 1:
            return vector_matrix[0]
        else:
            return vector_matrix

def topN_similar_by_vector(artists_vectors_df, aggr_vector, N = 20):
    '''
    predict top-N similar artists to the vector aggr_vector
    '''
    sim = cosine_similarity(aggr_vector.reshape(1,-1), list(artists_vectors_df['vector']))[0]

    df = artists_vectors_df.copy()
    df['score'] = sim

    df = df.sort_values(by = ['score'], ascending = False)

    sim_artists = df['person_id'][:N]
    sim_scores = df['score'][:N]

    return list(sim_artists), list(sim_scores)

def recommend_by_user(model, left_out_row, user_code_dict, code_person_dict, user_artist_matrix, N=20):
    '''
    Recommend the top N items, removing the users own liked items from
    the results (implicit library do it automatically)
    '''
    user_id = left_out_row['user_id']
    user_code = user_code_dict[user_id]
    rec = model.recommend(user_code, user_artist_matrix, N=N)
    rec_persons = [code_person_dict[code] for (code,score) in rec]
    return rec_persons

def recommend_by_average(test_df_row, artists_vectors_df, N=20):
    '''
    Custom recommender. Recommend most similar vectors from item factors (vectors)
    using cosine similarity
    '''
    persons_lst = test_df_row['persons_lst']

    aggr_v = aggregare_vectors(persons_lst, artists_vectors_df)

    N = len(set(persons_lst))+N

    if len(aggr_v)!=0:
        artists, scores = topN_similar_by_vector(artists_vectors_df, aggr_v, N=N)
        artists_new = []

        for ar in artists:
            if ar not in set(persons_lst):
                artists_new.append(ar)
        return(artists_new[:20])
    else:
        return([])