import time
import cv2
import torch
from ultralytics import YOLO
import supervision as sv
from supervision.draw.color import Color
from datetime import datetime
import os
import shutil
import sys
import logging
import numpy as np

from mqtt_jetson import connect_mqtt, publish, subscribe, get_code_recu_flag

names = {0: 'btl', 1: 'main'}

version = '0.0.3'
'''
    2023-10-27 12:30
    MQTT ok :
    ping-pong + version
    bouteille ok (btl count in)
    bouteille retirée (btl count out)
    bouteille doublée
    bouteille vue
    bouteille disparue (btl disparue après scan)

    Remove camera_status flag
'''

def get_unique_filename(base_filename):
    """
    Retourne un nom de fichier basé sur base_filename qui n'existe pas encore.
    Si base_filename existe, il ajoute (1), (2), etc.
    jusqu'à ce qu'un nom de fichier unique soit trouvé.
    """
    # Séparation du nom de fichier et de l'extension
    filename, extension = os.path.splitext(base_filename)

    counter = 1
    while os.path.exists(base_filename):
        base_filename = f"{filename}({counter}){extension}"
        counter += 1

    return base_filename

def concatenate_videos(video_path1, video_path2, output_path):

    # Ouverture du premier fichier vidéo
    cap1 = cv2.VideoCapture(video_path1)
    width = int(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = int(cap1.get(cv2.CAP_PROP_FPS))

    # Initialisation de l'écriture de la vidéo de sortie
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

    # L'écriture de la première vidéo
    while True:
        ret, frame = cap1.read()
        if not ret:
            break
        out.write(frame)

    cap1.release()

    # Ouverture du deuxième fichier vidéo
    cap2 = cv2.VideoCapture(video_path2)

    # Lecture et écriture du deuxième fichier vidéo
    while True:
        ret, frame = cap2.read()
        if not ret:
            break
        out.write(frame)

    cap2.release()
    out.release()

class StreamToLogger(object):
    def __init__(self, logger, log_level=logging.INFO):
        self.logger = logger
        self.log_level = log_level

    def write(self, buf):
        for line in buf.rstrip().splitlines():
            self.logger.log(self.log_level, line.rstrip())

    def flush(self):
        pass

class InfoImg:
    def __init__(self, img_type, timestamp, pos) -> None:
        self.img_type = img_type  # 'img_ini, img_fin, img_in, img_out
        self.timestamp = timestamp  # Timestamp de l'image
        self.pos = pos  # Position de l'objet dans l'image

    def printout(self):
        logging.info("-------- image info presents as follow --------")
        logging.info("obj stats - %s", self.img_type)
        logging.info("image  at - %s", self.timestamp)
        logging.info("obj is at - %s", self.pos)

class Objet:
    def __init__(self, class_id, id, pos_ini):
        global names
        self.class_id = names[class_id]
        self.id = id
        self.img_ini = InfoImg(img_type = "img_ini",
                               timestamp = datetime.now(),
                               pos = pos_ini)
        self.img_fin = InfoImg(img_type = "img_fin",
                               timestamp = datetime.now(),
                               pos = pos_ini)
        self.img_in_out = []

class InfoVideo:
    def __init__(self, tab):
        self.tab = tab
        self.tmp_ini = datetime.now()
        self.tmp_fin = datetime.now()
        self.obj_ids = set()
        self.obj = []

    @property
    def obj_nbs(self):
        return len(self.obj_ids)

    def printout(self):
        logging.info("---------------- video info presents as follow ----------------")
        logging.info("start  at - %s", self.tmp_ini)
        logging.info("finish at - %s", self.tmp_fin)
        logging.info("id of obj - %s", self.obj_ids)
        logging.info("nb of obj - %d", self.obj_nbs)
        for i, item in enumerate(self.obj_ids):
            logging.info("class obj - %s", self.obj[self.tab[item]].class_id)
            logging.info("id of obj - %d", self.obj[self.tab[item]].id)
            self.obj[self.tab[item]].img_ini.printout()
            self.obj[self.tab[item]].img_fin.printout()
            if len(self.obj[self.tab[item]].img_in_out):
                for j, img in enumerate(self.obj[self.tab[item]].img_in_out):
                    img.printout()
            else:
                logging.info("obj stats - Cette %s n'a jamais franchi", self.obj[self.tab[item]].class_id)

        logging.info("---------------------------------------------------------------")

# onction principale
def main(model_path, video_path, device, save_dir, show_flag, mqtt_flag):

    # Accès à la variable globale names
    global names, version

    # -------------------------------- zone definition -------------------------------- #
    # Configuration de la connexion MQTT si le flag mqtt est activé
    if mqtt_flag:
        # Adresse du courtier MQTT (adresse IP de connection vers la bbot)
        broker = '192.168.1.163'
        port = 1883 # Port défaut du courtier MQTT
        # Sujets pour la publication et l'abonnement
        topic_pub = "bbot/ihm"
        topic_sub = "bbot/ia"
        topic_pin = "bbot/ping"
        topic_pon = "bbot/pong"

        client_id = f'Jetson'
        # initialise mqtt client connection
        client = connect_mqtt(broker, port, client_id)
        subscribe(client, topic_sub, topic_pin, topic_pon, version)
        client.loop_start() # Démarrer la boucle MQTT

    # Journalisation des informations sur le modèle, la vidéo et le dispositif
    logging.info("yolov8 info - %s", model_path)
    logging.info("video input - %s", video_path)
    logging.info("device info - %s", device)
    logging.info("demos saved - %s", save_dir)

    # Configuration de l'entrée du modèle (flux vidéo de ffmpeg sur jetson)
    url = video_path
    cap = cv2.VideoCapture(url)

    # Si la caméra est perdue, reboot le jetson automatiquement
    if not cap.isOpened():
        raise ValueError(f"Failed to open video source: {url}, rebooting")

    frame_width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    frame_fps = int(cap.get(cv2.CAP_PROP_FPS))
    logging.info("stream  fps - %d", frame_fps)
    cap.release()
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    frame_fps = 30 if frame_fps == 90000 else frame_fps

    # Définition de la ligne séparation, taille de l'image originale (640, 480)
    LINE_START = sv.Point(240, 0)
    LINE_END = sv.Point(240, 480)

    # Définition du compteur et annotator liés à la ligne
    line_counter = sv.LineZone(start = LINE_START, end = LINE_END)
    line_color = Color(r = 224, g = 57, b = 151)
    line_annotator = sv.LineZoneAnnotator(thickness = 2, text_thickness = 2, text_scale = 1,
                                          color = line_color, text_offset = 10)
    box_annotator = sv.BoxAnnotator(thickness = 2, text_thickness = 1, text_scale = 0.5)

    # Construction du modèle YOLO, en important les paramètres
    model = YOLO(model_path, task = 'detect')

    # Initialisation des variables modifiables dans la boucle d'analyse
    in_count  = line_counter.in_count
    out_count = line_counter.out_count

    frame_length = 0
    tab = {}
    frame_avant = []
    frame_apres = []
    fix_length = 10
    is_ending  = fix_length

    dbl_btl_flag = False
    fst_btl_triche = False
    img_in_exit = False
    img_ini_tmp = datetime(1999, 10, 11)
    img_ini_pos = [0, 0, 0, 0]
    img_max_pos = 0
    filename_previous = None
    # -------------------------------- zone definition -------------------------------- #

    # Boucle d'analyse pour traiter chaque résultat de détection sur l'image
    for result in model.track(url, imgsz = 128, conf = 0.4, device = device,
                              half = False, stream = True, persist = True,
                              show = show_flag, verbose = False, tracker = "./bytetrack_stream.yaml"):

        # count frame number from the second image
        # to calculate treatment fps at the end of for lop
        if frame_length == 2:
            start_time = time.time()
        # elif frame_length == 600:
        #     break
        frame_length = frame_length + 1

        # save current pure img to frame0
        # prepare for img lists : 'frame_apres' and 'frame_avant'
        frame0 = result.orig_img

        # check no object detected or no btl(cls==0) detected
        # save these no objet frames {fix_length} as the begining or the end of one video
        # check whether to cut and save the current recording video, once no btl for {fix_length} consecutive images
        if result.boxes.id is None or result.boxes.id[(result.boxes.cls == 0).nonzero()] is None:

            # check if video 'out' is being recorded
            try:
                is_opened = out.isOpened()

            except UnboundLocalError:
                # video 'out' not defined yet
                # video not being recorded
                pass

            # video 'out' is being recorded
            # save {fix_length = 10} images into frame_apres
            # make {fix_length} minus 1, which makes {fix_length} rest images to be added into 'frame_après'
            else:
                if is_opened and is_ending > 0:
                    is_ending -= 1
                    frame_apres.append(frame0)

                # split video once no btl for {fix_length} consecutive images
                if is_opened and is_ending == 0:

                    # write {fix_length = 10} images into 'out' and 'out_org' video
                    for frame_a in frame_apres:
                        out.write(frame_a)
                        out_org.write(frame_a)

                    is_ending = fix_length
                    frame_apres = []

                    # video cut by image vide
                    # logging.info('video is saved as %s', filename)
                    logging.info('video cut by img vide')
                    out.release()
                    out_org.release()

                    # check if this video contains double bouteille triche
                    if dbl_btl_flag == True:

                        if mqtt_flag:
                            publish(client, topic_pub, msg = f"dbl btl-{filename_previous}-{datetime.now()}")

                        logging.info("TRICHE POTENTIELLE 1: %s", filename_previous)
                        logging.info("TRICHE POTENTIELLE 2: %s", filename)

                        video_path1 = filename_previous + '.mp4'
                        video_path2 = filename + '.mp4'
                        output_path = filename[0:len(save_dir)+1] + 'triche/' + filename[len(save_dir)+1:] + '.mp4'
                        concatenate_videos(video_path1, video_path2, output_path)

                    # check if this video contains bouteille disparue triche
                    # print('xy', np.min(detections[detections.class_id == 0].xyxy[:, [0, 2]]))
                    # print('code', get_code_recu_flag())
                    # print('img in', img_in_exit)
                    if get_code_recu_flag() and np.min(detections[detections.class_id == 0].xyxy[:, [0, 2]])<= 240 and img_in_exit == False:
                        get_code_recu_flag(change = True)
                        if mqtt_flag:
                            publish(client, topic_pub, msg = f"btl disparue-{datetime.now()}")
                        logging.info("1ERE BTL DISPARUE   : %s", filename)

                    info_video.tmp_fin = datetime.now()
                    # info_video.printout()

                    filename_previous = filename
                    filename = None
                    tab = {}
                pass

        # Objet(s) de cls==0 détectés par le modèle
        else:

            # Copie de frame originale, pour l'ajout des annotations sur l'image
            frame = frame0.copy()

            detections = sv.Detections.from_yolov8(result)
            detections.tracker_id = result.boxes.id.numpy().astype(int)

            # Ne suivre que les objets de (detections.class_id == 0)
            line_counter.trigger(detections = detections[detections.class_id == 0])

            # Ajout des annotations sur l'image
            labels = [f"#{tracker_id} {names[class_id]} {confidence:0.2f}"
                        for _, _, confidence, class_id, tracker_id in detections]
            frame = box_annotator.annotate(scene = frame, detections = detections, labels = labels)
            line_annotator.annotate(frame = frame, line_counter = line_counter)

            # check if reach the fin de video
            # separte video if new btl id is found in detections.tracker_id
            if len(tab) != 0 and set(tab.keys()).isdisjoint(set(detections.tracker_id)):

                # write {fix_length = 10} images into 'out' and 'out_org' video
                for frame_a in frame_apres:
                    out.write(frame_a)
                    out_org.write(frame_a)

                is_ending = fix_length
                frame_apres = []

                # video cut by new btl introduite
                # logging.info('video is saved as %s', filename)
                logging.info('video cut by new btl id found')
                out.release()
                out_org.release()

                # check if this video contains double bouteille triche
                if dbl_btl_flag == True:

                    if mqtt_flag:
                        publish(client, topic_pub, msg = f"dbl btl-{filename_previous}-{datetime.now()}")

                    logging.info("TRICHE POTENTIELLE 1: %s", filename_previous)
                    logging.info("TRICHE POTENTIELLE 2: %s", filename)

                    video_path1 = filename_previous + '.mp4'
                    video_path2 = filename + '.mp4'
                    output_path = filename[0:len(save_dir)+1] + 'triche/' + filename[len(save_dir)+1:] + '.mp4'
                    concatenate_videos(video_path1, video_path2, output_path)

                info_video.tmp_fin = datetime.now()
                # info_video.printout()

                filename_previous = filename
                filename = None
                tab = {}

            # début de video, car tab n'existe jamais ou tab est initalisé à vide set
            if len(tab) == 0 and len(detections[detections.class_id == 0].xyxy) > 0:
                current_time = datetime.now()
                # Création de fichier vidéo
                filename = (save_dir + '/'
                            + str(current_time.strftime('%Y%m%d%H%M%S')) + str(current_time.microsecond)[:3]
                            + '_btl_' + str(detections.tracker_id[0]))

                if mqtt_flag:
                    publish(client, topic_pub, msg = f"btl vue-{filename}-{current_time}")

                out = cv2.VideoWriter(filename + '.mp4', fourcc = fourcc,
                                      fps = frame_fps, frameSize = (frame_width, frame_height))

                filename_org = (save_dir + '/org/'
                            + str(current_time.strftime('%Y%m%d%H%M%S')) + str(current_time.microsecond)[:3]
                            + '_org_' + str(detections.tracker_id[0]))
                out_org = cv2.VideoWriter(filename_org + '.mp4', fourcc = fourcc,
                                      fps = frame_fps, frameSize = (frame_width, frame_height))

                # write {fix_length = 10} images into 'out' and 'out_org' video
                for frame_a in frame_avant:
                    out.write(frame_a)
                    out_org.write(frame_a)

                # Création des infos à noter concernant la vidéo et les objets détectés
                info_video = InfoVideo(tab)
                info_video.obj_ids.update(detections.tracker_id)
                for index, id in enumerate(detections.tracker_id):
                    info_video.obj.append(Objet(detections.class_id[index], id, detections.xyxy[index]))
                    tab[id] = len(info_video.obj) - 1

                # Vérifie si la vidéo contient une double bouteille triche
                fst_btl_triche = False
                dbl_btl_flag = False
                # print(abs(img_ini_tmp - datetime.now()).total_seconds())
                # print(img_in_exit)
                # print(img_ini_pos)
                if min(img_ini_pos[0], img_ini_pos[2]) <= 240 and img_max_pos >= 100 and abs(img_ini_tmp - datetime.now()).total_seconds() <= 3 and img_in_exit == False:
                    fst_btl_triche = True
                else:
                    fst_btl_triche = False

                img_ini_tmp = datetime.now()
                img_ini_pos = detections[detections.class_id == 0].xyxy[0]
                fst_btl_id  = detections[detections.class_id == 0].tracker_id[0]
                if len(detections[detections.class_id == 0].tracker_id) > 1:
                    for index, id in enumerate(detections[detections.class_id == 0].tracker_id):
                        if min(detections[detections.class_id == 0].xyxy[index][0], detections[detections.class_id == 0].xyxy[index][2]) <= min(img_ini_pos[0], img_ini_pos[2]):
                            img_ini_pos = detections[detections.class_id == 0].xyxy[index]
                            fst_btl_id  = id
                img_max_pos = max(img_ini_pos[0], img_ini_pos[2])
                # print(img_ini_pos)
                img_in_exit = False

            # enregistrement de video en cours / fichier "out" déjà existé
            else:
                is_ending = fix_length
                frame_apres = []
                if_existed = [id in info_video.obj_ids for id in detections.tracker_id]
                info_video.obj_ids.update(detections.tracker_id)

                for index, item in enumerate(if_existed):
                    # pour les ids jamais vus
                    if not item:
                        # info_video.obj_ids.update(detections.tracker_id)
                        info_video.obj.append(Objet(detections.class_id[index], detections.tracker_id[index], detections.xyxy[index]))
                        tab[detections.tracker_id[index]] = len(info_video.obj) - 1
                    # pour les ids déjà existés
                    else:
                        current_id = detections.tracker_id[index]
                        info_video.obj[tab[current_id]].img_fin = InfoImg(img_type = "img_fin",
                                                                          timestamp = datetime.now(),
                                                                          pos = detections.xyxy[index])
                        if current_id == fst_btl_id:
                            pos = detections.xyxy[index]
                            img_max_pos = max(img_max_pos, max(pos[0], pos[2]))

            out.write(frame)
            out_org.write(frame0)

            # Comparer le compteur d'entrées actuel avec le précédent
            if in_count != line_counter.in_count:
                img_in_exit = True

                # Mettre à jour le compte d'entrée
                in_count = line_counter.in_count
                if mqtt_flag:
                    publish(client, topic_pub, msg = f"btl ok-{datetime.now()}")
                logging.info("count numbers in  %d", line_counter.in_count)
                info_video.obj[tab[detections.tracker_id[0]]].img_in_out.append(InfoImg("img_in",
                                                                                datetime.now(),
                                                                                detections.xyxy[0]))
                current_time = datetime.now()
                file_img = (save_dir + '/img/'
                            + str(current_time.strftime('%Y%m%d%H%M%S')) + str(current_time.microsecond)[:3]
                            + '_btl_' + str(detections.tracker_id[0]))
                cv2.imwrite(file_img + '_in.png', frame)

                if fst_btl_triche == True:
                    fst_btl_triche = False
                    dbl_btl_flag = True

            # Comparer le compteur de sorties actuel avec le précédent
            elif out_count != line_counter.out_count:
                img_in_exit = True

                out_count = line_counter.out_count
                if mqtt_flag:
                    publish(client, topic_pub, msg = f"btl retirée-{datetime.now()}")
                logging.info("count numbers out %d", line_counter.out_count)
                info_video.obj[tab[detections.tracker_id[0]]].img_in_out.append(InfoImg("img_out",
                                                                                datetime.now(),
                                                                                detections.xyxy[0]))
                current_time = datetime.now()
                file_img = (save_dir + '/img/'
                            + str(current_time.strftime('%Y%m%d%H%M%S')) + str(current_time.microsecond)[:3]
                            + '_btl_' + str(detections.tracker_id[0]))
                cv2.imwrite(file_img + '_out.png', frame)

        # enregistrement des images avant la video
        frame_avant.append(frame0)
        while len(frame_avant) > fix_length:
            frame_avant.pop(0)

    try:
        is_opened = out.isOpened()
    except UnboundLocalError:
        # print("Video 'out' is not defined yet")
        pass
    else:
        if is_opened:
            for frame_a in frame_apres:
                out.write(frame_a)
                out_org.write(frame_a)

            is_ending = fix_length
            frame_apres = []

            # logging.info('video is saved as %s', filename)
            logging.info('video cut by end of for loop')

            out.release()
            out_org.release()

            # check if this video contains double bouteille triche
            if dbl_btl_flag == True:

                if mqtt_flag:
                    publish(client, topic_pub, msg = f"dbl btl-{filename_previous}-{datetime.now()}")

                logging.info("TRICHE POTENTIELLE 1: %s", filename_previous)
                logging.info("TRICHE POTENTIELLE 2: %s", filename)

                video_path1 = filename_previous + '.mp4'
                video_path2 = filename + '.mp4'
                output_path = filename[0:len(save_dir)+1] + 'triche/' + filename[len(save_dir)+1:] + '.mp4'
                concatenate_videos(video_path1, video_path2, output_path)

            info_video.tmp_fin = datetime.now()
            # info_video.printout()

            filename_previous = filename
            filename = None
            # id_tracking = -1
            tab = {}
        else:
            pass

    end_time = time.time()
    total_time = end_time - start_time
    if mqtt_flag:
        client.loop_stop()
    logging.info('time passed       %.2f', total_time)
    logging.info('time on one frame %.2f', total_time/frame_length)
    logging.info('processing fps    %.2f', frame_length/total_time)
    logging.info('total numbers in  %d', line_counter.in_count)
    logging.info('total numbers out %d', line_counter.out_count)

if __name__ == '__main__':

    # Si CUDA est disponible, on est sur jetson
    if torch.cuda.is_available():
        # Sur jetson le modèle est accéléré par TensorRT engine
        model_path = './weights/bestv8img128all.engine'
        device = torch.device(0)

        video_path = 'rtsp://localhost:8554/entrance'
        # video_path = './video/faux.mp4'
        # video_path = './video/vrai.mp4'

        show_flag = False
        mqtt_flag = True

    # Inférence du modèle sur l'ordi local
    else:
        model_path = './weights/bestv8img128all.pt'
        device = 'cpu'
        # Utiliser le CPU si CUDA n'est pas disponible

        show_flag = True
        mqtt_flag = False

        video_path = './00323-M/run2/db/202309151402_bbot00034_btl_dcp.mp4'
        video_path = './00323-M/run2/db/faux.mp4'

    # Enregistrement des résultats de toutes les exécutions du modèle dans ./demo
    dir = './demo'
    os.makedirs(dir, exist_ok=True)
    dir_list = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d)) and d.startswith('run')]

    max_number = -1
    if len(dir_list):
        for folder in dir_list:
            try:
                number = int(folder[3:])
                if number > max_number:
                    max_number = number
            except ValueError:
                pass

    # Pour chaque exécution, on crée un nouveau dossier 'run'
    save_dir = dir + '/run' + str(max_number + 1)
    # Dans 'run', on crée les dossiers /org, /img, /triche
    os.makedirs(save_dir)
    # Enregistrement des images au moment de btl passage
    os.makedirs(save_dir + '/img')
    # Enregistrement des vidéos de triche (double bouteilles)
    os.makedirs(save_dir + '/triche')
    # Enregistrement des vidéos pures de triche
    os.makedirs(save_dir + '/org')

    # Configuration de logging système
    logging.basicConfig(level=logging.INFO)
    log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

    log_file_handler = logging.FileHandler(save_dir + "/log.txt")
    log_file_handler.setFormatter(log_format)
    logging.getLogger().addHandler(log_file_handler)

    stdout_logger = logging.getLogger('STDOUT')
    sl = StreamToLogger(stdout_logger, logging.INFO)
    sys.stdout = sl

    stderr_logger = logging.getLogger('STDERR')
    sl = StreamToLogger(stderr_logger, logging.ERROR)
    sys.stderr = sl

    main(model_path, video_path, device, save_dir, show_flag, mqtt_flag)


from PIL import Image
from matplotlib import image
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

weights_save_path = './checkpoint/mnist.ckpt'

model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

model.load_weights(weights_save_path)

preNum = int(input('Input the number of test pictures: '))

for i in range(preNum):
    image_path = input('The path of the test picture: ')
    img = Image.open(image_path)
    image = plt.imread(image_path)
    plt.set_cmap('gray')
    plt.imshow(image)

    img = img.resize((28, 28), Image.ANTIALIAS)
    img_arr = np.array(img.convert('L'))
    img_arr = 255 - img_arr
    img_arr = img_arr/255.0
    x_predict = img_arr[tf.newaxis, ...]

    result = model.predict(x_predict)
    # 求取向量中值最大的一维的索引
    pred = tf.argmax(result, axis=1)

    print('\n')
    print(int(pred))
    plt.pause(5)
    plt.close()


import sys
# caution: path[0] is reserved for script path (or '' in REPL)
sys.path.append('../../')

import pytest

# from lab4 import main
# from lab4.TEACHER_parser import arith_expr
"""Test cases for parser analyzer."""

# -----------------------------------------------------------------------------
@pytest.mark.parametrize(
    "text, expected",
    [
        ("123", 3)
    ]
)
def test_get_parser(text, expected):
    """Get the next token from text starting from pos."""
    # Arrange
    # Act
    result = arith_expr(text)
    print("result")
    print(result)
    # Assert
    # assert result == expected

test_get_parser("123", 1)


import pygame
import random
import numpy as np
import matplotlib.pyplot as plt

pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True

num_episodes = 1
total_reward = 0

full_height = 80
duck_height = 40

player_height = full_height
player_width = 40

player_x = 400
player_y = 400

jump_target = 360  # Target y-coordinate for jumping
jump_speed = 0  # Initial jump speed
jump_acceleration = -0.5  # Acceleration for the jump
gravity = 0.5  # Gravity applied to the player

obstacle_width = 40
obstacle_height = 40
obstacle_x = 1240
obstacle_y = 440
obstacle_speed = 10  # Speed at which the obstacle moves to the left

on_ground = True
ascending = False  # Flag to track if the player is ascending or descending

game_over = False

score = 0

game_font = pygame.font.Font(None, 36)  # Font for displaying the score

add_score = True

replay_button = pygame.Rect(550, 300, 180, 60)

def spawn_obstacle():
    global obstacle_x, obstacle_y
    obstacle_x = 1240
    obstacle_y = 440

spawn_obstacle()

# Q-learning parameters
alpha = 0.7  # Learning rate
gamma = 0.9  # Discount factor
epsilon = 0.1  # Exploration rate

# Q-table
num_states = 2  # Number of states (actions: jump or duck)
num_distances = 1280  # Number of possible distances (based on the screen width)
num_actions = 2  # Number of actions (0: do nothing, 1: jump, 2: duck)
q_table = np.zeros((num_states, num_distances, num_actions))

episode_numbers = []
total_rewards = []

def choose_action(state, distance):
    if random.uniform(0, 1) < epsilon:
        # Explore: choose a random action
        return random.randint(0, num_actions - 1)
    else:
        # Exploit: choose the action with the highest Q-value for the current state
        return np.argmax(q_table[state, distance])

def get_distance():
    return obstacle_x - player_x

def update_q_table(state, action, reward, distance):
    global game_over

    if game_over:
        # Apply penalty for dying
        reward = -100

    # Update Q-value for the previous state-action pair using the Q-learning formula
    q_table[state, distance, action] = (1 - alpha) * q_table[state, distance, action] + alpha * (
            reward + gamma * np.max(q_table[state, distance]))

def perform_action(action):
    global on_ground, ascending, jump_speed, player_height, player_y

    if action == 1 and on_ground:
        on_ground = False
        ascending = True
        jump_speed = -10
    elif action == 2 and on_ground:
        player_height = duck_height
        player_y = 440
    elif on_ground:
        player_height = full_height
        player_y = 400

def update_game_state():
    global on_ground, player_y, jump_speed, ascending, player_height, game_over

    if not on_ground:
        if ascending:
            player_y += jump_speed
            jump_speed += jump_acceleration

            if player_y <= jump_target:
                ascending = False

        else:
            player_y += jump_speed
            jump_speed += gravity

        if player_y >= 400 and not on_ground:
            player_y = 400
            on_ground = True
            jump_speed = 0

def check_collision():
    global game_over

    player = pygame.Rect(player_x, player_y, player_width, player_height)
    obstacle = pygame.Rect(obstacle_x, obstacle_y, obstacle_width, obstacle_height)

    if player.colliderect(obstacle):
        game_over = True
        restart_game()

def update_score():
    global score, add_score, total_reward

    if obstacle_x < player_x and add_score:
        update_q_table(state, action, 50, next_state)
        total_reward += 1
        score += 50
        add_score = False

def display_episode_results(episode_number, total_reward):
    # Append the episode number and total reward to the lists
    episode_numbers.append(episode_number)
    total_rewards.append(total_reward)

    # Clear the previous plot and create a new one
    plt.figure()

    # Plot the data
    plt.plot(episode_numbers, total_rewards, label='Episode Results')

    # Fit a polynomial regression line
    trend = np.polyfit(episode_numbers, total_rewards, deg=1)
    trend_line = np.poly1d(trend)
    plt.plot(episode_numbers, trend_line(episode_numbers), color='red', label='Trend Line')

    plt.xlabel('Number of Episodes')
    plt.ylabel('Total Reward')
    plt.title('Episode Results')
    plt.legend()

    # Display the plot
    plt.show()

def restart_game():
    global on_ground, ascending, jump_speed, player_height, player_y, game_over, score, add_score, num_episodes, total_reward
    display_episode_results(num_episodes, total_reward)
    on_ground = True
    ascending = False
    jump_speed = 0
    player_height = full_height
    player_y = 400
    game_over = False
    score = 0
    add_score = True
    num_episodes += 1
    total_reward = 0
    spawn_obstacle()

def update_obstacle():
    global obstacle_x, add_score

    obstacle_x -= obstacle_speed

    if obstacle_x <= 0:
        add_score = True
        spawn_obstacle()

learning_status_font = pygame.font.Font(None, 24)  # Font for displaying the learning status

def display_statistics():
    statistics_text = [f"Episodes: {num_episodes}", f"Total Reward: {total_reward}", f"Distance: {get_distance()}"]
    # Add more statistics as needed

    x = 10
    y = 40
    line_height = 24

    for stat in statistics_text:
        stat_text = learning_status_font.render(stat, True, (0, 0, 0))
        screen.blit(stat_text, (x, y))
        y += line_height

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill("#f0efef")

    if not game_over:
        # Get the current state based on the player's position and whether they are on the ground or not
        state = int(on_ground)

        # Choose an action based on the current state
        action = choose_action(state, get_distance())

        update_obstacle()

        # Perform the chosen action
        perform_action(action)

        # Update the game state
        update_game_state()

        # Check for collision with obstacle
        check_collision()

        # Update the score
        update_score()

        player = pygame.Rect(player_x, player_y, player_width, player_height)
        obstacle = pygame.Rect(obstacle_x, obstacle_y, obstacle_width, obstacle_height)

        pygame.draw.rect(screen, "black", player)
        pygame.draw.rect(screen, "black", obstacle)

        # ... (rest of the code)

        # Update Q-table based on the current state, action, reward, and next state
        next_state = int(on_ground)
        reward = 1  # Placeholder for the reward
        update_q_table(state, action, reward, get_distance())

    display_statistics()

    pygame.display.flip()
    clock.tick(60)

pygame.quit()


import os
import unittest
import json
import random

from flask_sqlalchemy import SQLAlchemy

from flaskr import create_app
from models import setup_db, Question, Category

class TriviaTestCase(unittest.TestCase):
    """This class represents the trivia test case"""

    def setUp(self):
        """Define test variables and initialize app."""
        self.app = create_app()
        self.client = self.app.test_client
        self.database_name = "trivia_test"
        self.database_path = "postgresql://{}:{}@{}/{}".\
            format('postgres', '0000', 'localhost:5432', self.database_name)
        setup_db(self.app, self.database_path)

        # binds the app to the current context
        with self.app.app_context():
            self.db = SQLAlchemy()
            self.db.init_app(self.app)
            # create all tables
            self.db.create_all()

        self.new_question = {
            "question": "What actor did author Anne Rice first denounce, then praise in the role of her beloved Lestat?",
            "answer": "Tom Cruise",
            "difficulty": 4,
            "category": 5}

    def tearDown(self):
        """Executed after reach test"""
        pass

    """
    TODO
    Write at least one test for each test for successful operation and for expected errors.
    """

    def test_get_pagenated_questions(self):
        """TEST PAENATE QUESTION"""
        res = self.client().get('/questions')
        data = json.loads(res.data)

        self.assertEqual(res.status_code, 200)
        self.assertEqual(len(data['questions']), 10)
        self.assertTrue(data['total_questions'])

    def test_404_sent_requesting_invalid_page(self):
        res = self.client().get('/questions?page=1000')
        # print('response:', res)
        data = json.loads(res.data)

        self.assertEqual(res.status_code, 404)
        self.assertEqual(data['success'], False)
        self.assertEqual(data['message'], 'content not found')

    def test_delete_question(self):
        """TEST DELETE QUESTION"""
        res = self.client().delete('/questions/22')
        data = json.loads(res.data)
        qestion = Question.query.filter_by(id=22).one_or_none()

        self.assertEqual(res.status_code, 200)
        self.assertEqual(data['success'], True)
        self.assertEqual(data['deleted'], 22)
        self.assertTrue(data['questions'])
        self.assertTrue(data['total_questions'])
        self.assertEqual(qestion, None)

    def test_422_delete_invalid_question_id(self):
        """TEST FAILUIR DELETE QUESTION"""
        res = self.client().delete('/questions/500')
        # print('response:', res)
        data = json.loads(res.data)
        # print('data:', data)

        self.assertEqual(res.status_code, 422)
        self.assertEqual(data['success'], False)
        self.assertEqual(data['message'], 'unprocessable')

    def test_create_new_question(self):
        """TEST CREATE NEW QUESTION"""
        res = self.client().post('/questions', json=self.new_question)
        data = json.loads(res.data)

        self.assertEqual(res.status_code, 200)
        self.assertTrue(data['created'])
        self.assertTrue(data['total_questions'])

    def test_405_create_new_question_not_allowed(self):
        res = self.client().post('/questions/30', json=self.new_question)
        # print('response: ',res)
        # data = json.loads(res.data)
        # print('data: ', data)

        self.assertEqual(res.status_code, 405)
        # self.assertEqual(data['success'], False)
        # self.assertEqual(data['message'], "method not allowed")

    def test_search_questions(self):
        res = self.client().post('/questions', json={"searchTerm": "title"})
        data = json.loads(res.data)

        self.assertEqual(res.status_code, 200)
        self.assertTrue(data['total_questions'])
        self.assertEqual(len(data['questions']), 2)

    def test_invalid_search_questions(self):
        res = self.client().post('/questions', json={"searchTerm": "sunlight"})
        data = json.loads(res.data)

        self.assertEqual(res.status_code, 200)
        self.assertEqual(data['total_questions'], 0)
        self.assertEqual(len(data['questions']), 0)

    def test_get_questions_by_category(self):
        res = self.client().get('/categories/5/questions')
        data = json.loads(res.data)
        current_category = Category.query.filter_by(id=5).first_or_404()
        questions = Question.query.filter_by(category=5).all()

        self.assertEqual(res.status_code, 200)
        self.assertTrue(data['current_category'])
        self.assertTrue(data['questions'])
        self.assertTrue(len(data['questions']))

    def test_404_get_questions_beyond_category_ids(self):
        res = self.client().get('/categories/15/questions')
        # print('response:',res)
        data = json.loads(res.data)
        current_category = Category.query.filter_by(id=15).one_or_none()

        self.assertEqual(res.status_code, 404)
        self.assertEqual(current_category, None)
        self.assertEqual(data['message'], "content not found")
        self.assertEqual(data['success'], False)

    def test_play(self):
        res = self.client().post(
            '/quizzes',
            json={
                "previous_questions": [],
                "quiz_category": {
                    "id": 5,
                    "type": "Entertainment"}})

        data = json.loads(res.data)

        self.assertEqual(res.status_code, 200)
        self.assertTrue(data['question'])

# Make the tests conveniently executable
if __name__ == "__main__":
    unittest.main()


from pathlib import Path
from random import choice
import shutil
from PySide6.QtCore import QUrl
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QFileDialog, QMessageBox, QPushButton, QWidget
from pamet.desktop_app.config import pamet_data_folder_path
from pamet import desktop_app
from pamet.desktop_app import get_user_settings

from pamet.model.script_note import ScriptNote
from pamet.views.note.card.props_edit.widget import FixedTextEdit
from .name_seed_data import adjectives, fruits, animals

from .ui_props_widget import Ui_ScriptNotePropsWidget

FILE_SCHEMA = 'file://'

def random_name():
    return f'{choice(adjectives)}-{choice(fruits)}-{choice(animals)}'

class ScriptNotePropsWidget(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.ui = Ui_ScriptNotePropsWidget()
        self.ui.setupUi(self)

        self.edit_widget = parent

        # Locals
        self._unsaved_script_changes = False
        config = get_user_settings()
        self._scripts_folder = Path(config.scripts_folder)
        self._scripts_folder.mkdir(parents=True, exist_ok=True)

        # Add the note text edit
        self.text_edit = FixedTextEdit(self)
        self.ui.noteTextVerticalLayout.addWidget(self.text_edit)
        self.text_edit.textChanged.connect(self._handle_text_change)

        # Generate the create script buttons
        self._templates_folder = Path(config.script_templates_folder)
        for template in self._templates_folder.iterdir():
            button = QPushButton(template.stem)
            content = template.read_text()
            extension = template.suffix

            button.clicked.connect(lambda cnt=content, ext=extension: self.
                                   create_script_file(cnt, ext))
            self.ui.createScriptWidget.layout().addWidget(button)

        # Setup the UI according to the note props (is a script path set up)
        script_path = self.edit_widget.edited_note.script_path
        self.ui.scriptPathLineEdit.setText(str(script_path))
        if script_path is None:
            script_path = ''
        self.handle_script_path_change(str(script_path))

        self.ui.argumentsLineEdit.setText(self.edited_note.command_args)
        self.text_edit.setText(self.edited_note.text)

        # Connect the UI with the handlers
        self.ui.scriptPathLineEdit.textChanged.connect(
            self.handle_script_path_change)
        self.ui.scriptNameLineEdit.textChanged.connect(
            self.handle_script_name_change)
        self.ui.renamePushButton.clicked.connect(
            self.handle_rename_button_click)
        self.ui.openInEditorPushButton.clicked.connect(
            self._handle_open_in_editor_button_click)
        self.ui.openContainingFolderPushButton.clicked.connect(
            self._handle_open_containing_folder_button_click)
        self.ui.deletePushButton.clicked.connect(
            self._handle_delete_button_click)
        self.ui.chooseFilePushButton.clicked.connect(self._open_file_chooser)
        self.ui.argumentsLineEdit.textChanged.connect(
            self._handle_args_changed)
        self.ui.setDefaultScriptsFolder.clicked.connect(
            self._set_default_folder)
        self.ui.configureTemplatesButton.clicked.connect(
            self._configure_templates)
        self.ui.runPushButton.clicked.connect(
            lambda: desktop_app.script_runner.run(self.edited_note))
        self.ui.runInTerminalCheckbox.toggled.connect(
            self._handle_run_in_terminal_toggled)

    @property
    def edited_note(self) -> ScriptNote:
        return self.edit_widget.edited_note

    def create_script_file(self, content: str, extension: str):
        script_path = self._scripts_folder
        while script_path.exists():
            script_path = self._scripts_folder / random_name()
            script_path = script_path.with_suffix(extension)

        try:
            script_path.write_text(content)
            # script_path.chmod(script_path.stat().st_mode | stat.S_IEXEC)
        except Exception as e:
            QMessageBox(self, 'Could not write to file',
                        f'Could not create the script file. Exception: {e}')
            return

        self.ui.scriptPathLineEdit.setText(str(script_path))

    def _save_script_changes(self):
        script_path: Path = self.edited_note.script_path
        if not script_path:
            raise Exception

        script_path.write_text(self.edited_note.script_te)

    def _handle_text_change(self):
        self.edited_note.text = self.text_edit.toPlainText()

    def handle_script_path_change(self, new_path: str):
        old_path = self.edited_note.script_path

        if new_path.startswith(FILE_SCHEMA):
            new_path = new_path[len(FILE_SCHEMA):]
            self.ui.scriptPathLineEdit.setText(new_path)
            return

        new_path: Path = Path(new_path)
        self.edited_note.script_path = new_path

        if new_path.exists() and new_path.is_file():
            self.ui.pathErrorLabel.setText('Exists')
            self.ui.deletePushButton.setEnabled(True)
            self.ui.scriptNameLineEdit.setEnabled(True)
            self.ui.scriptNameLineEdit.setText(new_path.name)
            self.ui.openInEditorPushButton.setEnabled(True)
        else:
            if shutil.which(new_path):
                self.ui.pathErrorLabel.setText('Executable')
            else:
                self.ui.pathErrorLabel.setText('Missing')
            self.ui.deletePushButton.setEnabled(False)
            self.ui.scriptNameLineEdit.setEnabled(True)
            self.ui.scriptNameLineEdit.setText('')
            self.ui.openInEditorPushButton.setEnabled(False)

        note_text = self.edited_note.text
        # Keep the text matching the file name except if the user hasn't
        # made an edit (and they differ).
        if not note_text or (old_path and note_text == old_path.name):
            self.text_edit.setText(new_path.name)

    def handle_script_name_change(self, new_name: str):
        if new_name and new_name != self.edited_note.script_path.name:
            self.ui.renamePushButton.setEnabled(True)
        else:
            self.ui.renamePushButton.setEnabled(False)

    def handle_rename_button_click(self):
        script_path: Path = self.edited_note.script_path
        if not (script_path.exists() and script_path.is_file()):
            raise Exception(f'Cannot rename {script_path}. File missing.')

        new_name = self.ui.scriptNameLineEdit.text()
        new_path = script_path.parent / new_name
        try:
            # Make it possible to create a subdir on the spot
            new_path = new_path.resolve()
            new_path.parent.mkdir(parents=True, exist_ok=True)

            script_path.rename(new_path)
        except Exception as e:
            QMessageBox.warning(
                self, 'Error during rename',
                ('Could not rename the file {script_path} to {new_path}.'
                 f' Error: {e}'))
            return

        self.ui.scriptPathLineEdit.setText(str(new_path))

    def _handle_open_in_editor_button_click(self):
        QDesktopServices.openUrl(QUrl.fromLocalFile(str(self.edited_note.script_path)))

    def _handle_open_containing_folder_button_click(self):
        QDesktopServices.openUrl(QUrl.fromLocalFile(str(
            self.edited_note.script_path.parent)))

    def _handle_delete_button_click(self):
        path = self.edited_note.script_path
        try:
            path.unlink()
        except Exception as e:
            QMessageBox.warning(
                self, 'Error deleting file',
                f'Could not delete file {path}. Exception: {e}')
        self.ui.scriptPathLineEdit.setText('')

    def _open_file_chooser(self):
        path, _ = QFileDialog.getOpenFileName(self, 'Choose a script file',
                                              str(self._scripts_folder))
        if not path:
            return

        self.ui.scriptPathLineEdit.setText(path)

    def _handle_args_changed(self, new_args_text: str):
        self.edited_note.command_args = new_args_text

    def _set_default_folder(self):
        instructions_text = '''
        The config.json will be opened. You can change the scripts folder there.

        If you don't know what you're doing - it would be best to not touch it.
        '''
        QMessageBox.information(self, 'How to set the folder',
                                instructions_text)
        QDesktopServices.openUrl(QUrl.fromLocalFile(str(pamet_data_folder_path)))
        self.edit_widget._handle_esc_shortcut()

    def _configure_templates(self):
        instructions_text = '''
        You can add templates to the templates folder, which will be opened
        after you close this prompt.
        To restore the original templates - just delete the files and restart
        the app.
        '''
        QMessageBox.information(self, 'How to set the folder',
                                instructions_text)
        QDesktopServices.openUrl(QUrl.fromLocalFile(str(self._templates_folder)))
        self.edit_widget._handle_esc_shortcut()

    def _handle_run_in_terminal_toggled(self, checked: bool):
        self.edited_note.run_in_terminal = checked


from django.urls import resolve, reverse
from django.test import TestCase
from rest_framework import status
from rest_framework.test import APITestCase

import routing.views as views
from routing.models import (
    Configuration, DriveTimeNode, DriveTimePolygon,
    DriveTimeQuery, Ways, WaysVerticesPgr
)

class WaysVerticesPgrTests(APITestCase):
    def setUp(self):
        self.ways_vert_pgrdata = {
            'id': 2319,
            'osm_id': 41692755,
            'eout': None,
            'lon': -73.55962700,
            'lat': 42.51304600,
            'cnt': 3,
            'chk': 0,
            'ein': None,
            'the_geom': 'SRID=4326;POINT (-73.55962700000001 42.513046)'
        }
        data = self.ways_vert_pgrdata

        ways_vertices_pgr = WaysVerticesPgr.objects.create(
            **data
        )

    def test_post_ways_vertices_pgr(self):
        start_count = WaysVerticesPgr.objects.count()
        url = reverse('ways-vertices-pgr-list')
        data = self.ways_vert_pgrdata
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(WaysVerticesPgr.objects.count(), start_count)
        # self.assertEqual(WaysVerticesPgr.objects.all().order_by('-created_time')[:1].get().osm_id, 2344038654)

    def test_get_ways_vertices_pgr(self):
        start_count = WaysVerticesPgr.objects.count()
        ways_vertices_pgr = WaysVerticesPgr.objects.all().order_by('-lon')[:1].get()
        url = reverse('ways-vertices-pgr-detail', kwargs={'pk': ways_vertices_pgr.pk})
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['properties']['osm_id'], ways_vertices_pgr.osm_id)
        self.assertEqual(WaysVerticesPgr.objects.count(), start_count)

    def test_delete_ways_vertices_pgr(self):
        start_count = WaysVerticesPgr.objects.count()
        ways_vertices_pgr = WaysVerticesPgr.objects.all().order_by('-lon')[:1].get()
        url = reverse('ways-vertices-pgr-detail', kwargs={'pk': ways_vertices_pgr.pk})
        response = self.client.delete(url, {'id': ways_vertices_pgr.id}, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(WaysVerticesPgr.objects.count(), start_count)
        # self.assertIsNone(response.data)

class WaysTests(APITestCase):
    def setUp(self):
        ways_vertices_pgr = WaysVerticesPgr.objects.create(
            **RoutingApiUrlTest.WAYS_VERTICES_PGR_DATA
        )
        self.ways_data = {
            "osm_id": 5611497,
            "length": 0.00519409361291285,
            "length_m": 479.261682035277,
            "name": None,
            "cost": 0.00519409361291285,
            "reverse_cost": 0.00519409361291285,
            "cost_s": 34.5068411065399,
            "reverse_cost_s": 34.5068411065399,
            "rule": None,
            "one_way": 0,
            "oneway": "UNKNOWN",
            "x1": -73.559627,
            "y1": 42.513046,
            "x2": -73.555567,
            "y2": 42.515796,
            "maxspeed_forward": 50.0,
            "maxspeed_backward": 50.0,
            "priority": 2.5,
            "tag": None,
            "source": ways_vertices_pgr,
            "target": ways_vertices_pgr,
            "source_osm": ways_vertices_pgr,
            "target_osm": ways_vertices_pgr,
            "the_geom": (
                'SRID=4326;LINESTRING (' +
                '-73.55962700000001 42.513046, ' +
                '-73.559073 42.513495, -73.55913200000001 42.51377, ' +
                '-73.558581 42.513973, -73.558031 42.514089, -73.557303 42.51432, ' +
                '-73.55673299999999 42.514697, -73.55634000000001 42.515102, ' +
                '-73.555671 42.515696, -73.555567 42.515796)'
            )
        }
        data = self.ways_data
        ways = Ways.objects.create(
            **data
        )

    def test_post_ways(self):
        start_count = Ways.objects.count()
        ways = Ways.objects.all().order_by('-cost')[:1].get()
        url = reverse('ways-detail', kwargs={'pk': ways.pk})
        data = self.ways_data
        if not type(data['source']) is int:
            data['source'] = self.ways_data['source'].id
        if not type(data['target']) is int:
            data['target'] = self.ways_data['target'].id
        if not type(data['source_osm']) is int:
            data['source_osm'] = self.ways_data['source_osm'].id
        if not type(data['target_osm']) is int:
            data['target_osm'] = self.ways_data['target_osm'].id
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(Ways.objects.count(), start_count)
        # self.assertEqual(Ways.objects.all().order_by('-created_time')[:1].get().osm_id, 2344038654)

    def test_get_ways(self):
        start_count = Ways.objects.count()
        ways = Ways.objects.all().order_by('-cost')[:1].get()
        url = reverse('ways-detail', kwargs={'pk': ways.pk})
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['properties']['osm_id'], ways.osm_id)
        self.assertEqual(Ways.objects.count(), start_count)

    def test_delete_ways(self):
        start_count = Ways.objects.count()
        ways = Ways.objects.all().order_by('-cost')[:1].get()
        url = reverse('ways-detail', kwargs={'pk': ways.pk})
        response = self.client.delete(url, {'id': ways.pk}, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(Ways.objects.count(), start_count)
        # self.assertIsNone(response.data)

class DriveTimeNodeTests(APITestCase):
    def setUp(self):
        ways_vertices_pgr = WaysVerticesPgr.objects.create(
            **RoutingApiUrlTest.WAYS_VERTICES_PGR_DATA
        )
        self.drive_time_node_data = {
            'agg_cost': 0.031669950271386,
            'cost': 0.00307276130123879,
            'created_time': '2019-07-14T11:00:00.868265Z',
            'drive_time_query': None,
            'edge': 476826,
            'edited_time': '2019-07-14T11:00:00.868274Z',
            'lat': 41.796805,
            'lon': -74.740023,
            'node': 433019,
            'osm_id': 2344038654,
            'seq': 100,
            'the_geom': 'POINT(-74.740023 41.796805)',
            'ways_vertices_pgr': ways_vertices_pgr.id,
        }
        data = self.drive_time_node_data
        data['ways_vertices_pgr'] = WaysVerticesPgr.objects.get(id=ways_vertices_pgr.id)
        drive_time_node = DriveTimeNode.objects.create(
            **data
        )

    def test_post_drive_time_node(self):
        start_count = DriveTimeNode.objects.count()
        url = reverse('drive-time-node-list')
        data = self.drive_time_node_data
        if not type(data['ways_vertices_pgr']) is int:
            data['ways_vertices_pgr'] = self.drive_time_node_data['ways_vertices_pgr'].id
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(DriveTimeNode.objects.count(), start_count)
        # self.assertEqual(DriveTimeNode.objects.all().order_by('-created_time')[:1].get().osm_id, 2344038654)

    def test_get_drive_time_node(self):
        start_count = DriveTimeNode.objects.count()
        drive_time_node = DriveTimeNode.objects.all().order_by('-created_time')[:1].get()
        url = reverse('drive-time-node-detail', kwargs={'pk': drive_time_node.pk})
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['properties']['osm_id'], drive_time_node.osm_id)
        self.assertEqual(DriveTimeNode.objects.count(), start_count)

    def test_delete_drive_time_node(self):
        start_count = DriveTimeNode.objects.count()
        drive_time_node = DriveTimeNode.objects.all().order_by('-created_time')[:1].get()
        url = reverse('drive-time-node-detail', kwargs={'pk': drive_time_node.pk})
        response = self.client.delete(url, {'id': drive_time_node.id}, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(DriveTimeNode.objects.count(), start_count)
        # self.assertIsNone(response.data)

class DriveTimeQueryTests(APITestCase):
    def setUp(self):
        ways_vertices_pgr = WaysVerticesPgr.objects.create(
            **RoutingApiUrlTest.WAYS_VERTICES_PGR_DATA
        )
        self.drive_time_query_data = {
            'search_text': '50 Wolf Road, Albany, NY',
            'place_id': 104728296 ,
            'osm_type': 'secondary' ,
            'osm_id': 126673617 ,
            'lat': '42.7387471' ,
            'lon': '-73.7907121' ,
            'display_name': 'Wolf Road, Shakers, Town of Colonie, Albany County, New York, 12110-9998, USA' ,
            'importance': 0.61 ,
            'bounding_box': ['42.7385631' ,
            '42.7388332' ,
            '-73.7908288' ,
            '-73.790586'] ,
            'osm_class': 'highway' ,
            'the_geom': 'POINT(-73.7907121 42.7387471)',
        }
        data = self.drive_time_query_data
        data['ways_vertices_pgr_source'] = WaysVerticesPgr.objects.get(id=ways_vertices_pgr.id)
        drive_time_query = DriveTimeQuery.objects.create(
            **data
        )

    def test_post_drive_time_query(self):
        start_count = DriveTimeQuery.objects.count()
        url = reverse('drive-time-query-list')
        data = self.drive_time_query_data
        data['ways_vertices_pgr_source'] = WaysVerticesPgr.objects.get(id=data['ways_vertices_pgr_source'].id)
        if not type(data['ways_vertices_pgr_source']) is int:
            data['ways_vertices_pgr_source'] = self.drive_time_query_data['ways_vertices_pgr_source'].id
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(DriveTimeQuery.objects.count(), start_count)
        # self.assertEqual(DriveTimeQuery.objects.all().order_by('-created_time')[:1].get().osm_id, 126673617)

    def test_get_drive_time_query(self):
        start_count = DriveTimeQuery.objects.count()
        drive_time_query = DriveTimeQuery.objects.all().order_by('-created_time')[:1].get()
        url = reverse('drive-time-query-detail', kwargs={'pk': drive_time_query.pk})
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['properties']['osm_id'], drive_time_query.osm_id)
        self.assertEqual(DriveTimeQuery.objects.count(), start_count)

    def test_delete_drive_time_query(self):
        start_count = DriveTimeQuery.objects.count()
        drive_time_query = DriveTimeQuery.objects.all().order_by('-created_time')[:1].get()
        url = reverse('drive-time-query-detail', kwargs={'pk': drive_time_query.pk})
        response = self.client.delete(url, {'id': drive_time_query.id}, format='json')
        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(DriveTimeQuery.objects.count(), start_count)
        # self.assertIsNone(response.data)

class RoutingApiUrlTest(TestCase):
    def setUp(self):
        ways_vertices_pgr = WaysVerticesPgr.objects.create(
            **self.WAYS_VERTICES_PGR_DATA
        )
        config = Configuration.objects.create(
            **self.CONFIGURATION_DATA
        )
        drive_time_node = DriveTimeNode.objects.create(
            ways_vertices_pgr=ways_vertices_pgr,
            **self.DRIVE_TIME_NODE_DATA
        )
        ways = Ways.objects.create(
            tag=config,
            source=ways_vertices_pgr,
            target=ways_vertices_pgr,
            source_osm=ways_vertices_pgr,
            target_osm=ways_vertices_pgr,
            **self.WAYS_DATA
        )

    def test_drive_time_node_list_url(self):
        found = resolve('/routing/drive-time-nodes/')
        self.assertEqual(found.func.view_class, views.DriveTimeNodeList)

    def test_drive_time_node_detail_url(self):
        drive_time_node = DriveTimeNode.objects.get(osm_id=2344038654)
        found = resolve(f'/routing/drive-time-nodes/{drive_time_node.pk}/')
        self.assertEqual(found.func.view_class, views.DriveTimeNodeDetail)

    def test_drive_time_polygon_list_url(self):
        found = resolve('/routing/drive-time-polygons/')
        self.assertEqual(found.func.view_class, views.DriveTimePolygonList)

    def test_drive_time_polygon_detail_url(self):
        found = resolve('/routing/drive-time-polygons/1/')
        self.assertEqual(found.func.view_class, views.DriveTimePolygonDetail)

    def test_drive_time_query_list_url(self):
        found = resolve('/routing/drive-time-queries/')
        self.assertEqual(found.func.view_class, views.DriveTimeQueryList)

    def test_drive_time_query_detail_url(self):
        found = resolve('/routing/drive-time-queries/1/')
        self.assertEqual(found.func.view_class, views.DriveTimeQueryDetail)

    def test_ways_list_url(self):
        found = resolve('/routing/ways/')
        self.assertEqual(found.func.view_class, views.WaysList)

    def test_ways_detail_url(self):
        ways = Ways.objects.get(osm_id=5645519)
        found = resolve(f'/routing/ways/{ways.pk}/')
        self.assertEqual(found.func.view_class, views.WaysDetail)

    def test_ways_vertices_pgr_list_url(self):
        found = resolve('/routing/ways-vertices-pgr/')
        self.assertEqual(found.func.view_class, views.WaysVerticesPgrList)

    def test_ways_vertices_pgr_detail_url(self):
        ways_vertices_pgr = WaysVerticesPgr.objects.get(osm_id=212310248)
        found = resolve(f'/routing/ways-vertices-pgr/{ways_vertices_pgr.pk}/')
        self.assertEqual(found.func.view_class, views.WaysVerticesPgrDetail)

    def test_query_drive_time_url(self):
        found = resolve('/routing/drive-time/')
        self.assertEqual(found.func.view_class, views.QueryDriveTime)

    WAYS_VERTICES_PGR_DATA = {
        'chk': 0,
        'cnt': 1,
        'ein': None,
        'eout': None,
        'lat': '43.85515100',
        'lon': '-73.75416800',
        'osm_id': 212310248,
        'the_geom': 'POINT(-73.754168 43.855151)'
    }

    DRIVE_TIME_NODE_DATA = {
        'agg_cost': 0.031669950271386,
        'cost': 0.00307276130123879,
        'created_time': '2019-07-14T11:00:00.868265Z',
        'drive_time_query': None,
        'edge': 476826,
        'edited_time': '2019-07-14T11:00:00.868274Z',
        'lat': 41.796805,
        'lon': -74.740023,
        'node': 433019,
        'osm_id': 2344038654,
        'seq': 100,
        'the_geom': 'POINT(-74.740023 41.796805)'
    }

    WAYS_DATA = {
        'cost': 0.00137627993419783,
        'cost_s': 8.84425760474878,
        'length': 0.00137627993419783,
        'length_m': 118.6122549931,
        'maxspeed_backward': 48.28038,
        'maxspeed_forward': 48.28038,
        'name': 'Hendricks Street',
        'one_way': 0,
        'oneway': 'UNKNOWN',
        'osm_id': 5645519,
        'priority': 2.5,
        'reverse_cost': 0.00137627993419783,
        'reverse_cost_s': 8.84425760474878,
        'rule': None,
        'x1': -73.9696465,
        'x2': -73.97093,
        'y1': 42.7923714,
        'y2': 42.791878,
        'the_geom':
            'LINESTRING(-73.9696465 42.7923714, -73.970528 42.792006, ' +
            '-73.970601 42.791978, -73.97093 42.791878)'
    }

    CONFIGURATION_DATA = {
        'tag_id': 112,
        'tag_key': 'highway',
        'tag_value': 'residential',
        'priority': 2.5,
        'maxspeed': 50.0,
        'maxspeed_forward': 50.0,
        'maxspeed_backward': 50.0,
        'force': 'N'
    }


# coding: utf-8

from typing import Dict, List  # noqa: F401

from fastapi_cache import FastAPICache
from fastapi import (  # noqa: F401
    APIRouter,
    Body,
    Cookie,
    Depends,
    Form,
    Header,
    Path,
    Query,
    Response,
    Security,
    status,
)

from openapi_server.models.extra_models import TokenModel  # noqa: F401
from openapi_server.utils.vars import EMPIRICALMODEL_TYPE_NAME, EMPIRICALMODEL_TYPE_URI
from openapi_server.connector import query_manager
from fastapi_cache.decorator import cache

from openapi_server.models.empirical_model import EmpiricalModel
from openapi_server.security_api import get_token_BearerAuth

router = APIRouter()

@router.get(
    "/empiricalmodels",
    responses={
        200: {"model": List[EmpiricalModel], "description": "Successful response - returns an array with the instances of EmpiricalModel."},
    },
    tags=["EmpiricalModel"],
    summary="List all instances of EmpiricalModel",
    response_model_by_alias=True,
)
@cache(namespace="EmpiricalModel", expire=1800)
async def empiricalmodels_get(
    username: str = Query(None, description="Name of the user graph to query"),
    label: str = Query(None, description="Filter by label"),
    page: int = Query(1, description="Page number"),
    per_page: int = Query(100, description="Items per page", ge=1, le=200),
) -> List[EmpiricalModel]:
    """Gets a list of all instances of EmpiricalModel (more information in https://w3id.org/okn/o/sdm#EmpiricalModel)"""

    return query_manager.get_resource(

        username=username,label=label,page=page,per_page=per_page,

        rdf_type_uri=EMPIRICALMODEL_TYPE_URI,
        rdf_type_name=EMPIRICALMODEL_TYPE_NAME,
        kls=EmpiricalModel
        )

@router.delete(
    "/empiricalmodels/{id}",
    responses={
        202: {"description": "Deleted"},
        404: {"description": "Not Found"},
    },
    tags=["EmpiricalModel"],
    summary="Delete an existing EmpiricalModel",
    response_model_by_alias=True,
)
async def empiricalmodels_id_delete(
    id: str = Path(None, description="The ID of the EmpiricalModel to be retrieved"),
    user: str = Query(None, description="Username"),
    token_BearerAuth: TokenModel = Security(
        get_token_BearerAuth
    ),
) -> None:
    """Delete an existing EmpiricalModel (more information in https://w3id.org/okn/o/sdm#EmpiricalModel)"""

    await FastAPICache.clear(namespace="EmpiricalModel")
    return query_manager.delete_resource(
        id=id,
        user=user,

        rdf_type_uri=EMPIRICALMODEL_TYPE_URI,
        rdf_type_name=EMPIRICALMODEL_TYPE_NAME,
        kls=EmpiricalModel
        )

@router.get(
    "/empiricalmodels/{id}",
    responses={
        200: {"model": EmpiricalModel, "description": "Gets the details of a given EmpiricalModel"},
    },
    tags=["EmpiricalModel"],
    summary="Get a single EmpiricalModel by its id",
    response_model_by_alias=True,
)
@cache(namespace="EmpiricalModel", expire=1800)
async def empiricalmodels_id_get(
    id: str = Path(None, description="The ID of the EmpiricalModel to be retrieved"),
    username: str = Query(None, description="Name of the user graph to query"),
) -> EmpiricalModel:
    """Gets the details of a given EmpiricalModel (more information in https://w3id.org/okn/o/sdm#EmpiricalModel)"""

    return query_manager.get_resource(
        id=id,
        username=username,

        rdf_type_uri=EMPIRICALMODEL_TYPE_URI,
        rdf_type_name=EMPIRICALMODEL_TYPE_NAME,
        kls=EmpiricalModel
        )

@router.put(
    "/empiricalmodels/{id}",
    responses={
        200: {"model": EmpiricalModel, "description": "Updated"},
        404: {"description": "Not Found"},
    },
    tags=["EmpiricalModel"],
    summary="Update an existing EmpiricalModel",
    response_model_by_alias=True,
)
async def empiricalmodels_id_put(
    id: str = Path(None, description="The ID of the EmpiricalModel to be retrieved"),
    user: str = Query(None, description="Username"),
    empirical_model: EmpiricalModel = Body(None, description="An old EmpiricalModelto be updated"),
    token_BearerAuth: TokenModel = Security(
        get_token_BearerAuth
    ),
) -> EmpiricalModel:
    """Updates an existing EmpiricalModel (more information in https://w3id.org/okn/o/sdm#EmpiricalModel)"""

    await FastAPICache.clear(namespace="EmpiricalModel")
    return query_manager.put_resource(
        id=id,
        user=user,
        body=empirical_model,
        rdf_type_uri=EMPIRICALMODEL_TYPE_URI,
        rdf_type_name=EMPIRICALMODEL_TYPE_NAME,
        kls=EmpiricalModel
        )

@router.post(
    "/empiricalmodels",
    responses={
        201: {"model": EmpiricalModel, "description": "Created"},
    },
    tags=["EmpiricalModel"],
    summary="Create one EmpiricalModel",
    response_model_by_alias=True,
)
async def empiricalmodels_post(
    user: str = Query(None, description="Username"),
    empirical_model: EmpiricalModel = Body(None, description="Information about the EmpiricalModelto be created"),
    token_BearerAuth: TokenModel = Security(
        get_token_BearerAuth
    ),
) -> EmpiricalModel:
    """Create a new instance of EmpiricalModel (more information in https://w3id.org/okn/o/sdm#EmpiricalModel)"""

    await FastAPICache.clear(namespace="EmpiricalModel")
    return query_manager.post_resource(

        user=user,
        body=empirical_model,
        rdf_type_uri=EMPIRICALMODEL_TYPE_URI,
        rdf_type_name=EMPIRICALMODEL_TYPE_NAME,
        kls=EmpiricalModel
        )


from math import isclose
from typing import Tuple

import astropy.units as u
import hypothesis.strategies as st
import numpy as np
import pandas as pd
import pytest
from astropy.table import setdiff
from astropy.table.operations import join
from astropy.units.core import UnitConversionError
from hypothesis import assume
from hypothesis.core import given

from kanon.models.models import (
    long_of_the_tropical_mean_sun,
    mercury_equ_proportional_minutes,
)
from kanon.tables.htable import HTable, join_multiple
from kanon.tables.symmetries import Symmetry

sample = {"a": [1, 2, 3, 4, 5], "b": [5, 9, 12, 25, 26]}

def make_sample_table(index="a"):
    return HTable(sample, index=index)

def test_init():
    table = HTable(sample)
    assert table[2]["b"] == 12

    with pytest.raises(IndexError):
        table.get(3)

    table.add_index("b")
    assert table.get(5) == 1

gen_table_strategy = st.builds(
    HTable,
    st.lists(
        st.tuples(
            st.integers(min_value=int(-1e15), max_value=int(1e15)),
            st.floats(allow_nan=False, allow_infinity=False, width=16),
        ),
        min_size=2,
        unique_by=(lambda x: x[0]),
    ).map(lambda x: list(zip(*sorted(x)))),
    names=st.just(("A", "B")),
    index=st.just("A"),
)

@given(gen_table_strategy)
def test_symmetry(tab: HTable):
    assume(len(tab) > 1)
    tab.symmetry = [Symmetry("mirror", sign=-1)]
    df = tab.to_pandas()
    assert df["B"].iloc[0] == -df["B"].iloc[-1]
    tab.symmetry = [Symmetry("periodic")]
    df = tab.to_pandas()
    assert df["B"].iloc[0] == df["B"].iloc[len(tab)]

@given(
    gen_table_strategy.flatmap(
        lambda x: st.tuples(
            st.floats(
                min_value=float(min(x["A"])),
                max_value=float(max(x["A"])),
                allow_nan=False,
            ),
            st.just(x),
        )
    )
)
def test_interpolation(hypo: Tuple[float, HTable]):
    key, tab = hypo
    fres = np.interp(
        key, [float(x) for x in tab.columns[0]], [float(x) for x in tab.columns[1]]
    )
    sres = tab.get(key)
    assert isclose(fres, float(sres), abs_tol=1e9)

def test_apply():
    tab = make_sample_table()

    assert np.array_equal(
        tab.apply("b", lambda x: x + 2)["b"],
        np.add(tab["b"], np.array([2] * len(tab))),
    )

    tab_float = tab.apply("b", lambda x: x + 0.3)
    assert tab_float["b"].dtype == np.dtype("float64")
    tab_int = tab_float.apply("b", round, "rounded")
    assert tab_int["rounded"].dtype == np.dtype("int64")

    def tostr(x):
        return f"{x}a"

    tab_str = tab.apply("b", tostr)
    assert tab_str["b"].dtype.char == "U"

def test_index():
    tab = make_sample_table()

    assert tab.loc[1]["b"] == 5
    with pytest.raises(KeyError):
        tab.loc[6]

    new_tab = tab.copy(set_index="b")
    assert new_tab.loc[5]["a"] == 1
    with pytest.raises(KeyError):
        new_tab.loc[1]

def test_populate():
    tab = make_sample_table(index="b")

    pop_array = [i for i in range(5, 15)]

    populated = tab.populate(pop_array, method="interpolate")

    assert len(populated) == len(set(populated["b"]).union(pop_array))

    assert populated.loc[6]["a"] == tab.get(6)

def test_diff():
    tab = make_sample_table()

    assert list(tab.diff()["b"]) == [0, 4, 3, 13, 1]
    assert list(tab.diff(prepend=[3])["b"]) == [2, 4, 3, 13, 1]
    assert list(tab.diff(append=[24])["b"]) == [4, 3, 13, 1, -2]

    with pytest.raises(ValueError):
        tab.diff(prepend=[1, 1])

def test_fill():
    tab = make_sample_table()
    tab["b"] = tab["b"].astype("float64")

    tab = tab.populate([i / 4 for i in range(4, 16)])

    tab_filled = tab.fill("distributed_convex", (2, 4))
    assert list(tab_filled.loc[3:4]["b"]) == [12, 15, 18, 21, 25]

    tab_filled = tab.fill("distributed_concave", (2, 4))
    assert list(tab_filled.loc[3:4]["b"]) == [12, 16, 19, 22, 25]

    tab_unmasked = tab.filled(50)

    assert (
        len(setdiff(tab_unmasked, tab.fill("distributed_convex", (4, 5)).filled(50)))
        == 0
    )
    assert (
        len(
            setdiff(tab_unmasked, tab.fill("distributed_convex", (3.5, 3.5)).filled(50))
        )
        == 0
    )

    def fill_50(df: pd.DataFrame):
        return df.fillna(50)

    assert len(setdiff(tab_unmasked, tab.fill(fill_50))) == 0

    with pytest.raises(ValueError) as err:
        tab.fill("distributed_convex", (2.5, 4))
    assert str(err.value) == "First and last rows must not be masked"

    with pytest.raises(ValueError) as err:
        tab.fill("not a method", (2, 4))
    assert str(err.value) == "Incorrect fill method"

def test_join_multiple():
    data = [HTable({"a": x, "b": x}) for x in [[1, 2, 3], [8, 10, 13], [435, 13, 3.5]]]
    multiple_joined = join_multiple(*data, join_type="outer")
    single_joined = join(
        join(data[0], data[1], join_type="outer"), data[2], join_type="outer"
    )

    assert len(setdiff(multiple_joined, single_joined)) == 0

def test_plot():
    tab = make_sample_table()
    plot = tab.plot2d(".")
    assert len(plot) == 1
    assert plot[0].get_marker() == "."

def test_get_with_quantity():
    tab = make_sample_table()

    tab["a"].unit = u.degree

    assert tab.get(3) == 12
    assert tab.get(3 * u.degree) == 12

    assert tab.get(180 * u.arcmin) == 12

    with pytest.raises(UnitConversionError):
        assert tab.get(3 * u.deg_C)

def test_freeze():
    tab = make_sample_table()
    tab.freeze()
    assert tab.loc[3]["b"] == 12
    assert tab.get(3) == 12
    tab.loc[3]["b"] = 4
    assert tab.loc[3]["b"] == 4
    assert tab.get(3) == 12
    tab.unfreeze()
    assert tab.loc[3]["b"] == 4
    assert tab.get(3) == 4

def test_from_model():
    args = list(range(5))
    tab = HTable.from_model(long_of_the_tropical_mean_sun, args, (2,))
    assert tab.get(0) == long_of_the_tropical_mean_sun(0, 2)
    assert tab.get(1) == long_of_the_tropical_mean_sun(1, 2)
    assert len(tab) == 5

    tab_double = HTable.from_model(
        mercury_equ_proportional_minutes, args, (1, 2), arguments2=args
    )
    assert tab_double.get(1).get(1) == mercury_equ_proportional_minutes(1, 1, 1, 2)
    assert tab_double.get(2).get(2) == mercury_equ_proportional_minutes(2, 2, 1, 2)

    assert len(tab_double) == 5
    assert len(tab_double.get(0)) == 5

    assert tab_double.table_type == mercury_equ_proportional_minutes.table_type

def test_shift():
    tab = make_sample_table()

    assert tab.displace("b", -1)["b"][0] == 4

    shifted = tab.shift("a", 2)

    assert tab["a"][0] == shifted["a"][2]
    assert tab["a"][-1] == shifted["a"][1]

def test_double():
    args = list(range(5))

    tab = HTable.from_model(
        mercury_equ_proportional_minutes, args, (1, 2), arguments2=args + [6, 7]
    )

    df = tab.to_pandas()

    assert len(df) == 5
    assert len(df.loc[0]) == 7


import logging

from edx_rest_api_client.client import EdxRestApiClient
from edx_rest_api_client.exceptions import HttpClientError

logger = logging.getLogger(__name__)

class CourseStructureApiClient(EdxRestApiClient):
    """
    This class is a sub-class of the edX Rest API Client
    (https://github.com/edx/edx-rest-api-client).

    Details about the API itself can be found at
    https://openedx.atlassian.net/wiki/display/AN/Course+Structure+API.
    """
    DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

    def __init__(self, url, access_token, timeout):
        super(CourseStructureApiClient, self).__init__(url, oauth_access_token=access_token, timeout=timeout)

    @property
    def all_courses(self):
        courses = []
        page = 1

        while page:
            try:
                logger.debug('Retrieving page %d of course info...', page)
                response = self.courses.get(page=page, page_size=100)
                course_details = response['results']

                courses += course_details

                if response['next']:
                    page += 1
                else:
                    page = None
                    logger.debug('Completed retrieval of course info. Retrieved info for %d courses.', len(courses))
            except HttpClientError as e:
                logger.error("Unable to retrieve course data: %s", e)
                page = None
                break

        return courses


from dashboard.tests.base import UnitTest, reverse

from squalaetp.models import Xelon
from tools.models import TagXelon, Suptech, SuptechItem

class MixinsTest(UnitTest):

    def setUp(self):
        super(MixinsTest, self).setUp()
        xelon = Xelon.objects.create(numero_de_dossier='A123456789', vin=self.vin, modele_produit='produit',
                                     modele_vehicule='peugeot')
        SuptechItem.objects.create(name='Hot Line Tech')
        self.xelonId = str(xelon.id)

    def test_create_Tag_xelon_ajax_mixin(self):
        """
        Create TagXelon through BSModalCreateView.
        """
        self.add_perms_user(TagXelon, 'add_tagxelon')
        self.login()

        # First post request = ajax request checking if form in view is valid
        response = self.client.post(
            reverse('tools:tag_xelon_add'),
            data={
                'xelon': 'wrong_xelon',
                'calibre': True,
                'telecode': False,
                'comments': ''
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )

        # Form has errors
        self.assertTrue(response.context_data['form'].errors)
        # No redirection
        self.assertEqual(response.status_code, 200)
        # Object is not created
        tags = TagXelon.objects.all()
        self.assertEqual(tags.count(), 0)

        # Second post request = non-ajax request creating an object
        response = self.client.post(
            reverse('tools:tag_xelon_add'),
            data={
                'xelon': 'A123456789',
                'calibre': True,
                'telecode': False,
                'comments': ''
            }
        )

        # redirection
        self.assertEqual(response.status_code, 302)
        # Object is not created
        tags = TagXelon.objects.all()
        self.assertEqual(tags.count(), 1)

    def test_create_suptech_ajax_mixin(self):
        """
        Create Suptech through BSModalCreateView.
        """

        # First post request = ajax request checking if form in view is valid
        response = self.client.post(
            reverse('tools:suptech_add'),
            data={
                'xelon': 'A123456789',
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )

        # Form has errors
        self.assertTrue(response.context_data['form'].errors)
        # No redirection
        self.assertEqual(response.status_code, 200)
        # Object is not created
        suptechs = Suptech.objects.all()
        item = SuptechItem.objects.first()
        self.assertEqual(suptechs.count(), 0)

        # Second post request = non-ajax request creating an object
        response = self.client.post(
            reverse('tools:suptech_add'),
            data={
                'username': self.user.username,
                'xelon': 'A123456789',
                'item': item.id,
                'time': '5',
                'info': 'test',
                'rmq': 'test',
            },
        )

        # redirection
        self.assertEqual(response.status_code, 302)
        # Object is not created
        suptechs = Suptech.objects.all()
        self.assertNotEqual(suptechs.count(), 0)


'''
Author: littleherozzzx zhou.xin2022.code@outlook.com
Date: 2023-01-12 13:27:24
LastEditTime: 2023-02-01 11:19:38
Software: VSCode
'''
import os
import requests
import sys
import os
from urllib.parse import unquote
from prettytable import PrettyTable
from time import sleep
import datetime as dt
import hashlib
import base64
import json
sys.path.append(os.getcwd())

from config.config import ConfigParser

class Killer:
    def __init__(self):
        pass

    def init(self, configFile):
        self.loadConfig(configFile)
        self.__initSession()

    def loadConfig(self, configFile):
        self.configParser = ConfigParser(configFile)
        if not os.path.exists(configFile):
            self.configParser.createConfig()
        self.cfg = self.configParser.parseConfig()
        self.sessionCfg = self.cfg['session']
        self.urls = self.cfg['urls']
        self.seat_list = self.cfg["seat_list"]
        self.data = self.cfg["data"]
        self.settings = self.cfg["settings"]
        self.userInfo = self.cfg["user_info"]
        self.plans = self.cfg["plans"]
        self.plans = self.cfg["plans"]

    def saveConfig(self):
        self.cfg['seat_list'] = self.seat_list
        self.cfg['user_info'] = self.userInfo
        self.cfg['plans'] = self.plans
        self.cfg['plans'] = self.plans
        self.configParser.saveConfig(self.cfg)

    def __initSession(self):
        import urllib3
        urllib3.disable_warnings()
        self.session = requests.Session()
        self.session.headers.clear()
        self.session.headers = self.sessionCfg['headers']
        self.session.trust_env = self.sessionCfg['trust_env']
        self.session.verify = self.sessionCfg['verify']
        self.session.params = self.sessionCfg['params']

    def login(self):
        url = self.urls["login"]
        loginRes = self.session.post(url=url, data=self.userInfo).json()
        if loginRes["CODE"] == "ok":
            self.uid = loginRes["DATA"]["uid"]
            self.name = loginRes["DATA"]["user_info"]["name"]
        return loginRes["CODE"] == "ok"

    def __queryRooms(self):
        # 查询所有可用的房间类型，返回一个字典，键为房间名，值为房间对应的请求参数
        url = self.urls["query_rooms"]
        queryRoomsRes = self.session.get(url=url).json()
        rawRooms = queryRoomsRes["content"]["children"][1]["defaultItems"]
        rooms = {x["name"]: unquote(x["link"]["url"]).split('?')[1] for x in rawRooms}
        for room in rooms.keys():
            rooms[room] = self.session.get(url=self.urls["query_seats"] + "?" + rooms[room]).json()["data"]
            sleep(2)
        return rooms

    def __querySeats(self):
        #  查询每个房间的作为信息
        time = dt.datetime.now()
        if time.hour >= 22:
            time = time + dt.timedelta(days=1)
            time = time.replace(hour=11, minute=0, second=0)
        for room in self.rooms.keys():
            data = {
                "beginTime": time.timestamp(),
                "duration": 3600,
                "num": 1,
                "space_category[category_id]": self.rooms[room]["space_category"]["category_id"],
                "space_category[content_id]": self.rooms[room]["space_category"]["content_id"],
            }
            resp = self.session.post(url=self.urls["query_seats"], data=data).json()
            self.rooms[room]["floors"] = {x["roomName"]:x for x in resp["allContent"]["children"][2]["children"]["children"]}
            for floor in self.rooms[room]["floors"].keys():
                self.rooms[room]["floors"][floor]["seats"] = self.rooms[room]["floors"][floor]["seatMap"]["POIs"]
            sleep(2)
    def updateRooms(self):
        self.rooms = self.__queryRooms()
        self.__querySeats()
        return list(self.rooms.keys())

    def getFloorNamesByRoom(self, roomName):
        floors = self.rooms[roomName]["floors"]
        return list(floors.keys())

    def getSeatsByRoomAndFloor(self, roomName, floorName):
        seats = self.rooms[roomName]["floors"][floorName]["seats"]
        return seats

    def addPlan(self, roomName, beginTime, duration, seatsInfo, seatBookers):
        self.plans.append({
            "roomName": roomName,
            "beginTime": beginTime,
            "duration": duration,
            "seatsInfo": list(seatsInfo),
            "seatBookers": list(seatBookers),
        })

    def plan2data(self, plan):
        data = {}
        data["beginTime"] = int(plan["beginTime"].timestamp())
        data["duration"] = plan["duration"]*3600
        for i in range(len(plan["seatsInfo"])):
            data[f"seats[{i}]"] = plan["seatsInfo"][i]["seatId"]
        data["is_recommend"] = 0
        data["api_time"] = int(dt.datetime.now().timestamp())
        for i in range(len(plan["seatsInfo"])):
            data[f"seatBookers[{i}]"] = plan["seatBookers"][i]
        apiToken = f"post&/Seat/Index/bookSeats?LAB_JSON=1&api_time{data['api_time']}&beginTime{data['beginTime']}&duration{data['duration']}&is_recommend0&seatBookers[0]{data['seatBookers[0]']}&seats[0]{data['seats[0]']}"
        md5 = hashlib.md5(apiToken.encode("utf-8")).hexdigest()
        apiToken = base64.b64encode(md5.encode("utf-8"))
        return data, apiToken

    def showPlan(self):
        print(f"当前共有{len(self.plans)}个预约方案")
        if len(self.plans) == 0:
            return
        table = PrettyTable(["序号", "房间名", "楼层名", "座位号", "开始时间", "持续时间", "预约人"])
        for i, plan in enumerate(self.plans):
            seat = plan["seatsInfo"][0]
            table.add_row([f"{i+1}", seat['roomName'], seat['floorName'], ",".join([x["seatNum"] for x in plan["seatsInfo"]]), plan['beginTime'], str(plan['duration'])+"小时", ",".join([x["bookerName"] for x in plan["seatsInfo"]])])
        print(table)

    def deletePlan(self, index):
        index = set(index)
        self.plans = [x for i, x in enumerate(self.plans) if i not in index]
    def run(self, plan):
            data, Api_Token = self.plan2data(plan)
            url = self.urls["book_seat"]
            self.session.headers["Api-Token"] = Api_Token.decode()
            self.session.headers["Content-Length"] = "114"
            res = self.session.post(url=url, data=data).json()
            return res

    def changeTime(self, index, beginTime, duration):
        for i in index:
            self.plans[i]["beginTime"] = beginTime
            self.plans[i]["duration"] = duration

if __name__ == "__main__":

    killer = Killer()
    killer.init("./config/config.yaml")
    print(killer.login())
    killer.run(killer.plans[0])


#Exercício 3
import json

with open('dados.json') as file:
    data = json.load(file)

valoresDiarios = [element['valor'] for element in data]
valoresDiariosSemZero = [x for x in valoresDiarios if x != 0.0]

menorFatu = min(valoresDiariosSemZero)
print(f"Menor faturamento mensal foi: {menorFatu}")
maiorFatu = max(valoresDiariosSemZero)
print(f"Maior faturamento mensal foi: {maiorFatu}")

médiaMensal = sum(valoresDiariosSemZero)/len(valoresDiariosSemZero)
print(f"Média mensal foi de: {médiaMensal}")

count = 0

for x in valoresDiarios:
    if x > médiaMensal:
        count += 1

fatuMaiorMean = count

print(f"Número de dias no mês que o valor do faturamento diário foi superior à média mensal é de: {fatuMaiorMean}")


from art import logo
from replit import clear

def add(n1,n2):
  return n1+n2

def sub(n1,n2):
  return n1-n2

def mul(n1,n2):
  return n1*n2

def div(n1,n2):
  if n2!=0:
    return n1/n2

def calculate():
  print(logo)
  use_prev_output=True
  n1=int(input("What's the first number?: "))
  print(" +\n -\n *\n /")
  while use_prev_output:
    option = input('pick an operation: ')
    if option not in ['+','-','*','/']:
      print("Please select a valid operation.")
      clear()
      calculate()
    n2=int(input("What's the next number?: "))
    operations = {
    '+': add(n1,n2),
    '-': sub(n1,n2),
    '*': mul(n1,n2),
    '/': div(n1,n2)
    }
    output=operations[option]
    print(f"{n1} {option} {n2} = {output}")
    n1=output
    choice=input(f"Type 'y' to continue calculations with {output},'n' to start new calculations: ").lower()
    if choice == 'y':
      use_prev_output=True
    elif choice =='n':
      use_prev_output=False
      clear()
      calculate()
    else:
      use_prev_output=False
calculate()


import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt

mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

#n_train = mnist.train.num_examples  # 55,000
#n_validation = mnist.validation.num_examples  # 5000
#n_test = mnist.test.num_examples  # 10,000

left = 2.5
top = 2.5

fig = plt.figure(figsize=(10, 10))

for i in range(6):
    ax = fig.add_subplot(3, 2, i + 1)
    im = np.reshape(mnist.train.images[i, :], [28, 28])

    label = np.argmax(mnist.train.labels[i, :])
    ax.imshow(im, cmap='Greys')
    ax.text(left, top, str(label))

n_input = 784  # input layer (28x28 pixel)
n_output = 10  # output layer

learning_rate = 0.01
training_epochs = 25
batch_size = 100
display_step = 1

# Creating the model with the weights
X = tf.placeholder("float", [None, n_input])
W = tf.Variable(tf.zeros([n_input, n_output]))
b = tf.Variable(tf.zeros([n_output]))
Y = tf.matmul(X, W) + b

# Loss and optimizer.
Y_ = tf.placeholder(tf.float32, [None, 10])

# The raw formulation of cross-entropy,
#
#   tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.nn.softmax(y)),
#                                 reduction_indices=[1]))
#
# can be numerically unstable.
#
# So here we use tf.nn.softmax_cross_entropy_with_logits on the raw
# outputs of 'y', and then average across the batch.
cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=Y_, logits=Y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

# train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

sess = tf.InteractiveSession()
tf.global_variables_initializer().run()

# Training model
for _ in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(500)
    sess.run(train_step, feed_dict={X: batch_xs, Y_: batch_ys})

correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print("Model accuracy: ", accuracy.eval({X: mnist.test.images, Y_: mnist.test.labels}))
# print(sess.run(accuracy, feed_dict={X: mnist.test.images, Y_: mnist.test.labels}))

correct_vals = sess.run(correct_prediction,
                        feed_dict={X: mnist.train.images, Y_: mnist.train.labels})
pred_vals = sess.run(Y, feed_dict={X: mnist.train.images})

cntFalse = 0
for cv in correct_vals:
    if not cv:
        cntFalse += 1
print(cntFalse, "incorrect labels out of", len(correct_vals))

fig = plt.figure(figsize=(10, 10))
cntFalse = 0
for i, cv in enumerate(correct_vals):

    if not cv:
        cntFalse += 1

        ax = fig.add_subplot(3, 2, cntFalse)
        im = np.reshape(mnist.train.images[i, :], [28, 28])

        label = np.argmax(mnist.train.labels[i, :])
        pred_label = np.argmax(pred_vals[i, :])

        ax.imshow(im, cmap='Greys')
        ax.text(left, top, 'true=' + str(label) + ', predicted=' + str(pred_label))

    if cntFalse == 6:
        break

plt.show()


#creating database
import cv2, sys, numpy, os
haar_file = '/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml'
datasets = 'bryan_dp'  #All the faces data will be present this folder
sub_data = 'Bryan'     #These are sub data sets of folder, for my faces I've used my name use always different name for different people.
path = os.path.join(datasets, sub_data)
cv2.namedWindow('OpenCV')
print(path)
if not os.path.isdir(path):
    os.mkdir(path)
(width, height) = (130, 100) # defining the size of images
face_cascade = cv2.CascadeClassifier(haar_file)
if face_cascade.empty():
    print("Wrong path for haar_file")
    sys.exit()
webcam = cv2.VideoCapture(-1) #'0' is use for my webcam, if you've any other camera attached use '1'
if not webcam.isOpened():
    ret = webcam.open(0)
    if not webcam.isOpened():
        print("Camera not opened", ret)
        sys.exit()

# The program loops until it has 30 images of the face.
#count = 43
count = 1

#for i in range(count):
#    im = cv2.imread(datasets+'/'+str(i+1)+'.jpg')
#    if im == None:
#        print("ERROR", datasets+str(i+i)+'.jpg')
#        sys.exit()
while count <= 50:
    (_, im) = webcam.read()
    if im == None:
        print("Can't read image")
        sys.exit()
    gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)
    if faces == None:
        print("No face")
    else:
        print(faces)
    for (x,y,w,h) in faces:
        cv2.rectangle(im,(x,y),(x+w,y+h),(255,0,0),2)
        face = gray[y:y + h, x:x + w]
        face_resize = cv2.resize(face, (width, height))
        cv2.imwrite('%s/%s.png' % (path,count), face_resize)
        print(count)
        count += 1
    cv2.imshow('OpenCV', im)
    key = cv2.waitKey(10)
    if key == 27:
        break