"""Tools for executing multiple recourse experiment runs in parallel.

Given a list of run configs and a python mainfile capable of executing them,
it partitions the run configs into batches and launches a subprocess to
execute each batch."""
import sys
import os

#  Append MRMC/. to the path to fix imports.
sys.path.append(os.path.join(os.getcwd(), ".."))

import tempfile
from typing import Sequence, Mapping, Any, Optional, Tuple
import subprocess
import json

import numpy as np
import pandas as pd

class ParallelRunner:
    """Given a python experiment mainfile and a set of run configs to execute
    it on, run multiple instances of the mainfile over the run configs in
    parallel and collect their results.

    The mainfile should accept accept the command --config command line
    argument, which is the path to a .json file of run configs.

    The config.json file is created by the ParallelRunner and contains
    a list of dictionaries mapping configuration variables to values. Each
    dictionary corresponds to one run.

    Attributes:
        experiment_mainfile_path: The python mainfile to run in parallel.
        final_results_dir: The directory to write the final results to.
        scratch_dir: The process-local directory to write temporary resutls to.
        num_processes: The number of processes to execute in parallel.
        recourse_method: The recourse method to use when constructing the
            batch config file.
        dataset_name: The name of the dataset to use when constructing the
            batch config file.
        model_type: The model to use when constructing the batch config file.
        split: The data split to use when constructing the batch config file.
        use_slurm: Whether to use the SLURM distributed job scheduler.
        rng: The random generator used to distribute runs across processes.
            It does not effect the results, but may effect load balancing.
        verbose: Whether to print out progress."""

    def __init__(
        self,
        experiment_mainfile_path: str,
        final_results_dir: str,
        num_processes: int,
        recourse_method: str,
        dataset_name: str,
        model_type: str,
        split: str,
        use_slurm: bool = True,
        random_seed: Optional[int] = None,
        scratch_dir: Optional[str] = None,
        verbose: bool = False,
    ):
        """Creates a new ParallelRunner.

        Args:
            experiment_mainfile_path: The python mainfile to run in parallel.
            final_results_dir: The directory to write the final results to.
            num_processes: The number of child processes to run in parallel.
            recourse_method: The recourse method to use when constructing the
                batch config file.
            dataset_name: The name of the dataset to use when constructing the
                batch config file.
            model_type: The model to use when constructing the batch config
                file.
            split: The data split to use when constructing the batch config
                file.
            use_slurm: Whether to use the SLURM job scheduler. This should be
                True if you are executing this via the sbatch command.
            random_seed: The random seed to use when assigning runs to
                processes. This has no impact on experiment results.
            scratch_dir: The directory to use for storing temporary results.
        """
        self.experiment_mainfile_path = experiment_mainfile_path
        self.results_dir = final_results_dir
        self.scratch_dir = scratch_dir
        self.num_processes = num_processes
        self.use_slurm = use_slurm
        self.recourse_method = recourse_method
        self.dataset_name = dataset_name
        self.model_type = model_type
        self.split = split
        if not random_seed:
            random_seed = np.random.randint(0, 100000)
        self.rng = np.random.default_rng(random_seed)
        self.verbose = verbose

    def execute_runs(
        self, run_configs: Sequence[Mapping[str, Any]]
    ) -> Mapping[str, pd.DataFrame]:
        """Partitions a set of runs into batches and executes them in parallel.

        Each run_config is a dictionary whose format is specified by the
        mainfile which will be executed in parallel.

        Args:
            run_configs: The run configs to partition and execute in parallel.

        Returns:
            A mapping from filename to result DataFrame aggregated across all
            runs."""
        partitioned_run_configs = self.partition_runs(run_configs)
        scratch_dir = ""
        if self.scratch_dir:
            # ensure the scratch_dir is well-formatted.
            scratch_dir = os.path.join(self.scratch_dir, "")
        with tempfile.TemporaryDirectory(prefix=scratch_dir) as scratch_dir:
            scratch_results_dir = self.run_processes(
                partitioned_run_configs, scratch_dir
            )
            return self.collect_results(scratch_results_dir)

    def partition_runs(
        self, run_configs: Sequence[Mapping[str, Any]]
    ) -> Sequence[Sequence[Mapping[str, Any]]]:
        """Randomly partitions a list of run configs into batches to be
        executed in parallel."""
        shuffled_run_indices = np.arange(0, len(run_configs))
        self.rng.shuffle(shuffled_run_indices)

        # initialize a list of empty batches, one for each process
        partitioned_run_configs = [[] for _ in range(self.num_processes)]

        # iterate through and assign run configs into batches configs
        partition_index = 0
        for run_index in shuffled_run_indices:
            partitioned_run_configs[partition_index].append(
                run_configs[run_index]
            )
            partition_index = (partition_index + 1) % self.num_processes

        return partitioned_run_configs

    def run_processes(
        self,
        partitioned_run_configs: Sequence[Sequence[Mapping[str, Any]]],
        scratch_directory: str,
    ) -> str:
        """Runs many processes in parallel where each process is given a
        batch of run configs.

        Each process takes its batch from one of the partitions in the
        partitioned_run_configs argument.

        Args:
            partitioned_run_configs: The list of run config batches to be used
                by each process.
            scratch_directory: The temporary directory each process uses for
                input and output.

        Returns:
            The file directory where .csv results are written."""
        running_processes = self._launch_processes(
            partitioned_run_configs, scratch_directory
        )

        return self._monitor_processes(running_processes, scratch_directory)

    def collect_results(
        self, scratch_results_directory: str
    ) -> Mapping[str, pd.DataFrame]:
        """Loads and returns the final aggregated results from disk."""
        result_dfs = {}
        for result_name in os.listdir(scratch_results_directory):
            result_path = os.path.join(scratch_results_directory, result_name)
            stripped_result_name = os.path.splitext(result_name)[0]
            result_dfs[stripped_result_name] = pd.read_csv(result_path)
        return result_dfs

    def _launch_processes(
        self,
        partitioned_run_configs: Sequence[Sequence[Mapping[str, Any]]],
        scratch_directory: str,
    ) -> Mapping[subprocess.Popen, str]:
        """Asynchronously launches a subprocess for each of the run configs.

        Args:
            partitioned_run_configs: The batches of run configs to use in each
                subprocess.
            scratch_directory: A directory to use write the input and output
                of each subprocess to.

        Returns:
            A mapping from Process objects to the subdirectories they will
            write their results to."""
        running_processes: Mapping[subprocess.Popen, str] = {}
        for i, run_configs in enumerate(partitioned_run_configs):
            process_io_directory = os.path.join(
                scratch_directory, f"scratch_work_{i}"
            )
            os.mkdir(process_io_directory)
            p, process_results_directory = self._launch_process(
                run_configs, process_io_directory
            )
            running_processes[p] = process_results_directory
        return running_processes

    def _launch_process(
        self,
        run_configs: Sequence[Mapping[str, Any]],
        process_io_directory: str,
    ) -> Tuple[subprocess.Popen, str]:
        """Asynchronously launches a subprocess to execute a batch of
        run_configs.

        Args:
            run_configs: The run configs to execute.
            process_io_directory: The directory to use for writing the process
                input and output.

        Returns:
            A tuple of created process and final results path."""
        process_results_directory = os.path.join(
            process_io_directory, "results"
        )
        os.mkdir(process_results_directory)

        process_config_filename = self._write_process_config(
            process_io_directory, run_configs
        )

        process_cmd = self._format_subprocess_command(
            process_config_filename, process_results_directory
        )

        return (
            subprocess.Popen([process_cmd], shell=True),
            process_results_directory,
        )

    def _write_process_config(
        self,
        process_io_directory: str,
        run_configs: Sequence[Mapping[str, Any]],
    ) -> str:
        """Writes the run configs to a config file for the subprocess to read
        and returns the config file path."""
        process_config = {
            "run_configs": run_configs,
            "experiment_name": "experiment_scratch_work",
            "recourse_method": self.recourse_method,
            "dataset_name": self.dataset_name,
            "model_type": self.model_type,
            "split": self.split,
        }
        process_config_filename = os.path.join(
            process_io_directory, "config.json"
        )
        with open(process_config_filename, "w") as f:
            json.dump(process_config, f)
        return process_config_filename

    def _format_subprocess_command(
        self, process_config_filename: str, results_dir: str
    ) -> str:
        """Formats the command to launch a subprocess.

        If using SLURM, launches the subprocess as an srun step."""
        process_cmd = ""
        if self.use_slurm:
            process_cmd = "srun -N 1 -n 1 --exclusive "
        process_cmd += (
            f"python {self.experiment_mainfile_path} "
            f"--config {process_config_filename} --results_dir {results_dir} "
            "--only_csv"
        )
        return process_cmd

    def _monitor_processes(
        self,
        running_processes: Mapping[subprocess.Popen, str],
        scratch_directory: str,
    ) -> str:
        """Monitors the running subprocesses, aggregating their results as they
        terminate."""
        # Prepare the aggregated results directory
        aggregated_results_directory = os.path.join(
            scratch_directory, "results"
        )
        os.mkdir(aggregated_results_directory)

        if self.verbose:
            print(
                f"Waiting for {len(running_processes)} processes to finish..."
            )

        while running_processes:
            # Check for terminated processes and aggregate their results
            terminated_processes = []
            for process in running_processes:
                if process.poll() is not None:
                    if process.poll() != 0:
                        raise RuntimeError("One of the processes failed!")
                    self._aggregate_results(
                        aggregated_results_directory,
                        running_processes[process],
                    )
                    terminated_processes.append(process)
            for process in terminated_processes:
                del running_processes[process]
                if self.verbose:
                    remaining_procs = len(running_processes)
                    print(
                        f"Waiting for {remaining_procs} processes to finish..."
                    )
        return aggregated_results_directory

    def _aggregate_results(
        self, aggregated_results_directory: str, new_results_directory: str
    ) -> None:
        """Concatenates the newly generated results with the previous results.

        The newly merged results are saved over the original aggregated
        results."""
        for result in os.listdir(new_results_directory):
            result_path = os.path.join(new_results_directory, result)
            if not os.path.isfile(result_path):
                raise RuntimeError(
                    (
                        f"Result aggregation found directory "
                        f"{result} but only supports .csv files."
                    )
                )
            if not result.endswith(".csv"):
                raise RuntimeError(
                    (
                        f"Result aggregation found file {result} but only "
                        "supports .csv files."
                    )
                )
            new_results = pd.read_csv(result_path)
            aggregated_result_path = os.path.join(
                aggregated_results_directory, result
            )
            if not os.path.exists(aggregated_result_path):
                aggregated_results = new_results
            else:
                old_results = pd.read_csv(aggregated_result_path)
                aggregated_results = pd.concat(
                    [old_results, new_results]
                ).reset_index(drop=True)
            aggregated_results.to_csv(aggregated_result_path, index=False)


# coding: utf-8
# This Pipeline is a simple example
import sys

import luigi

import pandas as pd

from sklearn.model_selection import train_test_split

from sklearn.externals import joblib

from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

from sklearn.pipeline import make_pipeline

class IrisData(luigi.ExternalTask):
    def output(self):
        return luigi.LocalTarget("./data/iris.csv")

class TrainTestSplit(luigi.Task):
    def requires(self):
        return IrisData()

    def output(self):
        return {
            'X_train': luigi.LocalTarget("./data/X_train.csv"),
            'X_test': luigi.LocalTarget("./data/X_test.csv"),
            'y_train': luigi.LocalTarget("./data/y_train.csv"),
            'y_test': luigi.LocalTarget("./data/y_test.csv")
        }

    def run(self):
        cols = [
            "Sepal_Length",
            "Sepal_Width",
            "Petal_Length",
            "Petal_Width",
            "Species"
        ]

        features = [
            "Sepal_Length",
            "Sepal_Width",
            "Petal_Length",
            "Petal_Width"
        ]

        iris_df = pd.read_csv(self.input().path, names=cols)

        X = iris_df[features]
        y = iris_df["Species"]

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1234)

        X_train.to_csv(self.output()['X_train'].path, index = False, header = True)
        X_test.to_csv(self.output()['X_test'].path, index = False, header = True)
        y_train.to_csv(self.output()['y_train'].path, index = False, header = True)
        y_test.to_csv(self.output()['y_test'].path, index = False, header = True)

class TrainModel(luigi.Task):
    algorithm = luigi.Parameter()

    def requires(self):
        return TrainTestSplit()

    def output(self):
        return {
            'model': luigi.LocalTarget("./models/" + self.algorithm + "_model.pkl"),
            'metadata': luigi.LocalTarget("./models/" + self.algorithm + "_metadata.txt")
        }

    def run(self):
        X_train = pd.read_csv(self.input()['X_train'].path)
        y_train = pd.read_csv(self.input()['y_train'].path)

        if self.algorithm == 'SVC':
            model = SVC(kernel='linear', C=1.0)
        elif self.algorithm == 'RF':
            model = RandomForestClassifier()
        else:
            lr = LogisticRegression(solver='newton-cg',
                                    multi_class='multinomial',
                                    random_state=1)

            model = make_pipeline(StandardScaler(), lr)

        model.fit(X_train, y_train)

        joblib.dump(model, self.output()['model'].path)

        with self.output()['metadata'].open('w') as meta:
            meta.write(str(model))

class IrisPipeline(luigi.WrapperTask):
    def requires(self):
        yield TrainModel(algorithm='SVC')
        yield TrainModel(algorithm='RF')
        yield TrainModel(algorithm='LR')

if __name__ == "__main__":
    luigi_args = ['IrisPipeline',
                  '--local-scheduler',
                  '--workers', '4']
    luigi.run(luigi_args)


from tkinter import *
from tkinter import filedialog
from PIL import Image
from PIL import ImageTk
import cv2
import numpy as np
import contour_label as cnt
import shape_util as sh
from bradley import bradley

path = "M14_004.tif"
# path = "PC3_Ctrl_003.tif"
ypad = 50
thres = 0

# current label
curr_label = 0
# origin label before manual selection
origin = 0
# size of display window
window_size = 960
# image with EC circles
circles_left = 0
circles_right = 0

# image before EC circles with labels in BGR
before_circles = 0
ori_right = 0

# lable array to save
to_save = 0
# number of ECs
EC_count = 0

adj_image = 0

###
# Change labels
###
def change(arg):
    global panelA, points, imgA, imgB, panelB, mixed, curr_label, to_save, before_circles, ori_right
    # compose new points
    new_points = []
    for p in sh.points:
        new_points.append(p.x)
        new_points.append(p.y)
    if arg == "resetpoly":
        points = [[new_points[i], new_points[i+1]] for i in range(0,len(new_points),2)]
        points = np.array(points, np.int32)
        # initialize blank canvas
        blank = np.empty_like(curr_label)
        cv2.fillPoly(blank, [points], (1,1,1))
        # reset polygon region
        curr_label[blank == (1,1,1)] = origin[blank==(1,1,1)]
    elif arg == "resetall":
        curr_label = origin
        ori_right = Image.fromarray(adj_image)
        ori_right.thumbnail((window_size,window_size))
        ori_right = ImageTk.PhotoImage(ori_right)
        panelB.delete("all")
        imgB = panelB.create_image((0,0), image = ori_right, anchor = "nw")
    else:
        # add to original labels
        label_im = cnt.add_label(curr_label, new_points, arg)
        curr_label = label_im

    to_save = curr_label

    # blend again
    label = Image.fromarray(curr_label)
    label.thumbnail((window_size,window_size))
    gray = cv2.cvtColor(adj_image, cv2.COLOR_GRAY2RGB)
    gray = Image.fromarray(gray)
    gray.thumbnail((window_size,window_size))
    mixed = Image.blend(gray, label, 0.3)
    before_circles = np.copy(mixed)
    mixed = ImageTk.PhotoImage(mixed)

    # delete original img
    panelA.delete("all")
    imgA = panelA.create_image((0,0), image = mixed, anchor = "nw")
    sh.reset()
    panelA.bind("<Button-1>", sh.draw)
    return imgA, imgB

def delete():
    global panelA,imgA
    # reset points and shapes arrays
    sh.points = []
    sh.shape = 0
    panelA.delete("all")
    panelA.bind("<Button-1>", sh.draw)
    # put image back
    imgA = panelA.create_image((0,0), image = orig_mix, anchor = "nw")

#### read in image and convert that to binary ####
def read_im(image, ww, wh, factor = 1):
    global adj_image
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    bw_ratio = np.count_nonzero(image[image < np.mean(image)])/(image.shape[0]*image.shape[1])
    if bw_ratio > 0.5:
        image = (255 - image)
    # clahe = cv2.createCLAHE(clipLimit=10.0, tileGridSize=(8,8))
    # image = clahe.apply(image)
    adj_image = np.copy(image)

    # set threshold and create binary image
    gray = convert_im(image, thres)

    # cv to PIL
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    image = Image.fromarray(image)

    # thumbnail
    image_resize = image.copy()
    image_resize.thumbnail((ww,wh))
    image_resize = ImageTk.PhotoImage(image_resize) # to Tk

    # original image to tk
    image.thumbnail((window_size,window_size))
    image = ImageTk.PhotoImage(image)

    return image, image_resize, gray

#### returns binarized tk photoImage ####
def convert_im(image, threshold = 100):
    global curr_label, origin, thresh, before_circles
    # median = np.median(image)
    # ret,gray = cv2.threshold(image,median-20,255,cv2.THRESH_TRUNC)
    # gray = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,25,2)

    gray = bradley(image, 150,150,10)

    # update threshold global variable
    thresh = np.copy(gray)
    thresh = Image.fromarray(thresh)
    thresh.thumbnail((window_size,window_size))
    thresh = np.array(thresh)

    # label image for cell and chr and search region
    curr_label= cnt.find_contour(image, gray)

    # shrink images
    image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
    gray = Image.fromarray(image)
    gray.thumbnail((window_size,window_size))
    label = Image.fromarray(curr_label)
    label.thumbnail((window_size,window_size))
    curr_label = np.array(label)
    origin = np.copy(curr_label)

    # blend
    gray = Image.blend(gray, label, 0.3)
    before_circles = np.copy(gray)

    gray = ImageTk.PhotoImage(gray)
    return gray

#### scroll at the same time ####
def scroll_x(*args):
    panelA.xview(*args)
    panelB.xview(*args)

#### scroll at the same time ####
def scroll_y(*args):
    panelA.yview(*args)
    panelB.yview(*args)

#### terminates program ####
def quit_prog():
    global root
    root.destroy()

#### Mark selected as chromosomal region ####
def chr_area():
    change("chr")

#### Mark selected as cell region ####
def mask():
    change("cell")

#### Reset selected region ####
def reset_poly():
    change("resetpoly")

def reset_all():
    change("resetall")

def discard():
    quit_prog()

def save():
    quit_prog()

def detect_EC():
    global thresh, panelA, imgA, imgB, circles_left, before_circles, EC_count, panelB, circles_right

    tmp = np.copy(before_circles)

    # adjust adj_image size
    ori_image = Image.fromarray(adj_image)
    ori_image.thumbnail((window_size,window_size))
    ori_image = np.array(ori_image)

    # detect EC
    circles_left, EC_count, circles_right= cnt.detect_EC(tmp, thresh, curr_label, ori_image)
    print("Number of ECs: " + str(EC_count))

    # convert circles
    circles_left = Image.fromarray(circles_left)
    circles_left = ImageTk.PhotoImage(circles_left)
    circles_right = Image.fromarray(circles_right)
    circles_right = ImageTk.PhotoImage(circles_right)

    # reset panelA
    panelA.delete("all")
    imgA = panelA.create_image((0,0), image = circles_left, anchor = "nw")
    sh.reset()
    panelA.bind("<Button-1>", sh.draw)
    # reset panelB
    panelB.delete("all")
    imgB = panelB.create_image((0,0), image = circles_right, anchor = "nw")
    return imgA, imgB

def clear_EC():
    global panelA, imgA, imgB, panelB, circles_right, circles_left

    # assign back the origin images
    circles_left= Image.fromarray(before_circles)
    circles_left = ImageTk.PhotoImage(circles_left)
    circles_right = Image.fromarray(adj_image)
    circles_right.thumbnail((window_size,window_size))
    circles_right = ImageTk.PhotoImage(circles_right)

    # delete original img
    panelA.delete("all")
    imgA = panelA.create_image((0,0), image = circles_left, anchor = "nw")
    sh.reset()
    panelA.bind("<Button-1>", sh.draw)
    panelB.delete("all")
    imgB = panelB.create_image((0,0), image = circles_right, anchor = "nw")
    return imgA

root = Tk()

image = cv2.imread(path)

img, img_small, mixed = read_im(image, 240, 300)
orig_mix = mixed
w = 1200
h = 650
rw = img_small.width()
rh = img_small.height()

# defind root height and width
root.geometry("%dx%d" % (rw, rh))
root.resizable(0,0)

# new window with parallel large images
new = Toplevel()
x = root.winfo_x()
y = root.winfo_y()
new.title("Select Polygon")
new.geometry("%dx%d%+d%+d" % (w,h,x + 50,y+50))
new.resizable(0,0)

# put thumbnail in root
thumb = Label(root, image = img_small)
thumb.pack()

frameA = Frame(new)
frameA.grid(row = 0, column = 0, columnspan = 3, rowspan = 5)

# create scrollbars
scx_A = Scrollbar(frameA, orient = HORIZONTAL, command=scroll_x)
scx_A.grid(row=1, column=0, columnspan=4, sticky=E+W)

scy_A = Scrollbar(frameA, orient = VERTICAL, command=scroll_y)
scy_A.grid(row=0, column=4, sticky=N+S)

scx_B = Scrollbar(frameA, orient = HORIZONTAL, command=scroll_x)
scx_B.grid(row=1, column=5, columnspan=4, sticky=E+W)

scy_B = Scrollbar(frameA, orient = VERTICAL, command=scroll_y)
scy_B.grid(row=0, column=9, sticky=N+S)

# put the other two side by side on two scrollable canvases
panelA = Canvas(frameA, width=w//2-20, height=h-70,
                scrollregion=(0,0,img.width(),img.height()),
                xscrollcommand=scx_A.set,
                yscrollcommand=scy_A.set)
panelA.grid(row=0, column=0, columnspan=4)
imgA = panelA.create_image((0,0), image = mixed, anchor = "nw")

# bind mouse event
panelA.bind("<Button-1>", sh.draw)

panelB = Canvas(frameA, width=w//2-20, height=h-70,
                scrollregion=(0,0,img.width(),img.height()),
                xscrollcommand=scx_B.set,
                yscrollcommand=scy_B.set)
panelB.grid(row=0, column=5, columnspan=4)
imgB = panelB.create_image((0,0), image = img, anchor = "nw")

# create frame for buttons
frameB = Frame(new)
frameB.grid(row=5,column=0)

quit_button = Button(frameB, text = "Quit", command = quit_prog)
quit_button.grid(row = 5, column=0, padx=20, pady=10)

quit_button3 = Button(frameB, text = "Chr", command = chr_area)
quit_button3.grid(row = 5, column=2, padx=20, pady=10)

quit_button4 = Button(frameB, text = "Mask", command = mask)
quit_button4.grid(row = 5, column=3, padx=20, pady=10)

quit_button5 = Button(frameB, text = "Reset Polygon", command = reset_poly)
quit_button5.grid(row = 5, column=4, padx=20, pady=10)

quit_button7 = Button(frameB, text = "Reset All", command = reset_all)
quit_button7.grid(row = 5, column=5, padx=20, pady=10)

quit_button8 = Button(frameB, text = "Detect ECs", command = detect_EC)
quit_button8.grid(row = 5, column=6, padx=20, pady=10)

quit_button9 = Button(frameB, text = "Clear ECs", command = clear_EC)
quit_button9.grid(row = 5, column=7, padx=20, pady=10)

quit_button10 = Button(frameB, text = "Discard", command = change)
quit_button10.grid(row = 5, column=8, padx=20, pady=10)

quit_button11 = Button(frameB, text = "Continue", command = save)
quit_button11.grid(row = 5, column=9, padx=20, pady=10)

root.mainloop()


from . import api
from flask import render_template, jsonify, request
import http
import os
import sys
CURPATH = os.path.dirname(os.path.abspath(__file__))  # /project/web/api/
PROJPATH = os.path.dirname(os.path.dirname(CURPATH))  # /project/
INCPATH = os.path.join(PROJPATH, 'Include')  # /project/Include
sys.path.extend([CURPATH, PROJPATH, INCPATH])
sys.path = list(set(sys.path))
from Include import get_home

@api.route('/packet/logger', methods=['GET', 'POST'])
def packet_logger():
    return render_template('packet_log.html')

@api.route('/packet/logger/update', methods=['POST'])
def packet_logger_update():
    home = get_home()
    p1 = '
'.join([' '.join(['%02X' % y for y in x]) for x in home.packets_energy[::-1]])
    p2 = '
'.join([' '.join(['%02X' % y for y in x]) for x in home.packets_control[::-1]])
    p3 = '
'.join([' '.join(['%02X' % y for y in x]) for x in home.packets_smart_recv[::-1]])
    return jsonify({'energy': p1, 'control': p2, 'smart_recv': p3})

@api.route('/packet/logger/clear/<target>', methods=['POST'])
def packet_log_clear(target):
    home = get_home()
    if target == 'energy':
        home.packets_energy.clear()
    elif target == 'control':
        home.packets_control.clear()
    elif target == 'smart_recv':
        home.packets_smart_recv.clear()
    return '', http.HTTPStatus.NO_CONTENT

@api.route('/packet/logger/<device>/enable/<target>', methods=['POST'])
def packet_log_enable(device, target):
    home = get_home()
    req = request.get_data().decode(encoding='utf-8')
    value = int(req[6:].strip()) if 'value=' in req else 1
    if device == 'energy':
        if target == '31':
            home.enable_log_energy_31 = bool(value)
        elif target == '41':
            home.enable_log_energy_41 = bool(value)
        elif target == '42':
            home.enable_log_energy_42 = bool(value)
        elif target == 'D1':
            home.enable_log_energy_d1 = bool(value)
        elif target == 'room1':
            home.enable_log_energy_room_1 = bool(value)
        elif target == 'room2':
            home.enable_log_energy_room_2 = bool(value)
        elif target == 'room3':
            home.enable_log_energy_room_3 = bool(value)
        home.packets_energy.clear()
    elif device == 'control':
        if target == '28':
            home.enable_log_control_28 = bool(value)
        elif target == '31':
            home.enable_log_control_31 = bool(value)
        elif target == '61':
            home.enable_log_control_61 = bool(value)
        home.packets_control.clear()
    return '', http.HTTPStatus.NO_CONTENT


import dht
import errno
import gc
import machine
import os
import time
import ujson as json
import ubinascii as binascii

from config import read_config, write_config
from logging import log_info, log_exception
from mqtt import mqtt_create, mqtt_connect, mqtt_publish_message, mqtt_publish_state_message, get_epoch, mqtt_check_msg, mqtt_disconnect
from syncedtime import sync_time_with_ntp, get_epoch

# Test reception e.g. with:
# mosquitto_sub -t foo_topic

# suscribe thermostat ctrl
# - turn fan on (set a safety max time?)
# - turn fan off
# - turn ac on (will turn fan on too) (set a safety max time?)
# - turn ac off (will turn fan off too)
# - turn fan on for a period of time
# - turn ac on for a period of time
# - XXX turn ac on at a temperature once DHT22 is supported
# - XXX Schedules?

# publish ac topic
# - fan starts
# - fan stops
# - ac starts
# - ac stops
# - XXX temperature when it changes above some delta/every min once DHT22 is supported

# Safety rules:
# - always start the fan when AC is started
# - always start the fan first a few seconds before AC
# - wait a safety interval (1min?) since the last AC stop
# - don't run AC for more than 30 mins straight
# - don't allow setting temps below 70F
# - when following a temp, start the AC when it's above the temp plus safety margin, stop when it's below the temp minus safety margin

config_filename = "lessmostat.cfg"

def sub_cb(client, topic, msg):
    """

    Start at the given temp or higher
    { "state" : "on", temp="26" }

    Set fan to "auto" or "on"
    { "state" : "auto" }
    { "state" : "on" }

    XXX TODO complex scheduling
    messages
    topic: x/lessmostat/control/fan msg: { timestamp: , state: "on" | "off",  tod :, dow , temp : degs }
    topic: x/lessmostat/control/ac msg: { timestamp: , state: "on" | "off", tod :,  dow, temp : degs }
    topic: x/lessmostat/control/reset:
    topic: x/lessmostat/control/state: force state publish

    state: "on" | "off" set the device to on or off
    start_dow : start day of the week from 0 to 6, null for today
    start_time : 24 hour and minute start time, null for now
    start_temp : null for any temp

    { "state": "on" } Start
    { "state": "off" } Stop

    Start at the given temp or higher
    { "state" : "on", temp="26" }
    Stop at the given temp or lower
    { "state" : "off", temp="24" }

    Run for one hour
    { "state" : "on", tod: now() }
    { "state" : "off", tod: now()+1 hour }

    set morning and evening temps
    { "state" : "on", tod: 10am, temp="26" }
    { "state" : "off", tod: 8pm, temp="24" }
    { "state" : "on", tod: 8pm, temp="28" }
    { "state" : "off", tod: 10am, temp="26" }

    Stop on sundays 10am
    { "state" : "off", tod:10am, dow:6 }

    The controller will impose safety limits and then resume when the safety stop has been done:
    - ac run less than 30 mins straight
    - ac run with 5 mins between stop / start
    - fan run less than 12 hours straight
    - fan run with 1 mins between stop / start

    sunrise and sunset

    https://sunrise-sunset.org/api
    https://stackoverflow.com/questions/19615350/calculate-sunrise-and-sunset-times-for-a-given-gps-coordinate-within-postgresql

    request = Request('http://api.sunrise-sunset.org/json?lat=25.7741728&lng=-80.19362&formatted=0')
    response = urlopen(request)
    timestring = response.read()

    utcsunrise = timestring[34:39]
    utcsunset = timestring[71:76]
    utcmorning = timestring[182:187]
    utcnight = timestring[231:236]

    Get lat/long from https://sunrise-sunset.org/search?location=miami
    """
    log_info("callback for topic %r msg %r" % (topic, msg))

    try:
        d = json.loads(msg)

        if (topic.endswith("/state")):
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/ac")):
            state["config"]["ac_rules"] = [
                { "state" : "on", "temp" : d["temp"] },
            ]
            # XXX Should this publish only the delta?
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/fan")):
            state["config"]["fan_rules"] = [
                { "state" : d["state"] },
            ]
            # XXX Should this publish only the delta?
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/store_preset")):
            # Copy the current rules into the given preset
            preset_index = max(0, min(d["index"], max_presets - 1))
            state["config"]["presets"][preset_index] = {
                "fan" : state["config"]["fan_rules"][0],
                "ac" : state["config"]["ac_rules"][0],
            }
            # XXX Should this publish only the delta?
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/mode")):
            # Switch heating/cooling mode

            # As a safety measure, don't start/stop AC when switching modes,
            # 1) Ignore switching if the ac is already working
            # 2) set the target temperature to the current one before switching
            # Note switching to the same mode will cause the temperature to be reset
            if (state["ac"] == "on"):
                log_info("Ignoring control/mode request %s while ac is on" % d["state"])

            else:
                # XXX This assumes there's only one rule
                state["config"]["ac_rules"] = [
                    { "state" : "on", "temp" : state["sensor"]["temp"] },
                ]
                if (d["state"] == "cooling"):
                    currentMode = "cooling"
                else:
                    currentMode = "heating"
                state["config"]["mode"] = currentMode
                # XXX Should this publish only the delta?
                mqtt_publish_state_message(client, state)

    except Exception as e:
        log_exception("Exception handling topic %r message %r" % (topic, msg), e)

topic_root = "apartment/lessmostat/"

# See http://www.icstation.com/esp8266-wifi-channel-relay-module-smart-home-remote-control-switch-android-phone-control-transmission-distance-100m-p-12592.html
relay_1_on = [0xA0, 0x01, 0x1, 0xA2]
relay_1_off = [0xA0, 0x01, 0x00, 0xA1]
relay_2_on = [0xA0, 0x02, 0x01, 0xA3]
relay_2_off = [0xA0, 0x02, 0x00, 0xA2]

def uart_write(uart, b):
    uart.write(b)
    # XXX Looks like some delay is needed between uart writes otherwise the
    #     second write is lost and the relay state is not modified
    #     This is the case even with some code between uart_write invocations
    time.sleep(1)

fan_on = bytes(relay_2_on)
fan_off = bytes(relay_2_off)
def turn_fan(uart, client, on):
    fan_state = state["fan"]
    # Uptime accumulation assumes there are no redundant calls
    assert ((on and (fan_state != "on")) or ((not on) and fan_state != "off"))
    if (on):
        # XXX Should prevent somewhere it's not trying to re-enable fan before the
        #     safety idle period
        uart_write(uart, fan_on)
        fan_state = "on"

    else:
        uart_write(uart, fan_off)
        fan_state = "off"

    state["fan"] = fan_state
    prev_fan_mod_ts = state["fan_mod_ts"]
    state["fan_mod_ts"] = get_epoch()
    if (not on):
        # Accumulate uptime
        state["fan_uptime"] += (state["fan_mod_ts"] - prev_fan_mod_ts)

    mqtt_publish_message(client, "info/fan", { 'state' : fan_state, 'mod_ts' : state["fan_mod_ts"], 'uptime' : state["fan_uptime"] })

ac_on = bytes(relay_1_on)
ac_off = bytes(relay_1_off)
def turn_ac(uart, client, on):
    ac_state = state["ac"]
    # Uptime accumulation assumes there are no redundant calls
    assert ((on and (ac_state != "on")) or ((not on) and ac_state != "off"))
    if (on):
        # Always turn fan on before ac
        if (state["fan"] != "on"):
            log_info("ac forcing fan on")
            turn_fan(uart, client, on)

        # XXX Should prevent somewhere it's not trying to re-enable ac before
        #     the safety idle period

        uart_write(uart, ac_on)
        ac_state = "on"

    else:
        uart_write(uart, ac_off)
        ac_state = "off"

        # Leave the fan on, let it turn off depending on the rules

    state["ac"] = ac_state
    prev_ac_mod_ts = state["ac_mod_ts"]
    state["ac_mod_ts"] = get_epoch()
    if (not on):
        # Accumulate uptime
        state["ac_uptime"] += (state["ac_mod_ts"] - prev_ac_mod_ts)

    mqtt_publish_message(client, "info/ac", { 'state' : ac_state, 'mod_ts' : state["ac_mod_ts"], 'uptime' : state["ac_uptime"] })

state = {
    # Current state
    "start_ts" : None,

    "ac" : "off",
    "ac_mod_ts" : None,
    "ac_uptime" : 0,

    "fan" : "off",
    "fan_mod_ts" : None,
    "fan_uptime" : 0,

    "sensor" : { "humid" : None, "temp" : None },

    # Configuration state
    # XXX This may want to save the mod_ts so it doesn't continuously
    #     restart the AC. Alternatively always wait the safety period when
    #     restarting the program
    "config" : {
        "mqtt_broker" : "192.168.8.200",
        # The AC has a single rule which is to
        # - in cooling mode, start at the given temperature + hi_threshold and
        #   stop at the temperature - lo_threshold
        # - in heating mode, start at the given temperature - lo_threshold and
        #   stop at the temperature + hi_threshold
        "ac_rules" : [
            { "state" : "on", "temp" : 30 },
        ],
        # The fan has a single rule which is to set the fan to "on" (always
        # on or "auto" (match ac state)
        # XXX Allow setting the fan on a timer ("on", 15-30-60-120 min, "auto")
        #     and then revert to auto
        # XXX The fan counter could also show a guess on how long it will take
        #     to bring to the desired temp. Or move to a counter in the
        #     idle/cooling/heating state
        "fan_rules" : [
            { "state" : "auto" }
        ],
        # Hi and low thresholds in 1/10th of a celsius degree (ie in cooling
        # mode will turn on at temp + hi threshold and off at temp - low
        # threshold, in heating will turn on at temp - low and off at temp + hi)
        "lo_threshold_decidegs" : 4,
        "hi_threshold_decidegs" : 4,

        # heating/cooling mode
        "mode" : "cooling",

        # XXX Should this store the thresholds too?
        "presets" : [
            { "fan" : { "state" : "auto" }, "ac" : { "state" : "on", "temp" : 30 } },
            { "fan" : { "state" : "auto" }, "ac" : { "state" : "on", "temp" : 30 } },
            { "fan" : { "state" : "auto" }, "ac" : { "state" : "on", "temp" : 30 } },
        ],
    }
}
max_presets = len(state["config"]["presets"])

def main():
    try:
        log_info("Reading initial configuration")
        read_config(config_filename, state)
        log_info("Initial state is %r" % state)

        # Fetch some constant values from the config
        hi_threshold_decidegs = state["config"]["hi_threshold_decidegs"]
        lo_threshold_decidegs = state["config"]["lo_threshold_decidegs"]
        mqtt_broker = state["config"]["mqtt_broker"]

        # Update time with NTP
        sync_time_with_ntp()

        client_id = binascii.hexlify(machine.unique_id())

        # Now that we have an NTP time, initialize state times
        now_ts = get_epoch()
        state["start_ts"] = now_ts
        state["ac_mod_ts"] = now_ts
        state["fan_mod_ts"] = now_ts

        log_info("Initializing relays uart")
        uart = machine.UART(0, baudrate=115200, bits=8, parity=None, stop=1)
        # Detach REPL from UART so UART can be used for the relays
        log_info("Detaching UART from repl")
        os.dupterm(uart, 1)

        # The DHT is connected to the 5V, GND and RX (gpio 3)
        # Steal the RX pin from the uart, see
        # https://forum.micropython.org/viewtopic.php?t=6669
        log_info("Initializing DHT sensor")
        dht_sensor = dht.DHT22(machine.Pin(3, machine.Pin.IN))
        # DHT22 is known to timeout the first few times measure() is called, retry
        num_retries = 10
        while (num_retries > 0):
            try:
                num_retries -= 1
                dht_sensor.measure()
                break
            except OSError as e:
                if (e.errno == errno.ETIMEDOUT):
                    log_info("DHT sensor timed out, retrying")

                else:
                    raise

        # On power unplug reset the relays are closed, but on machine reset they
        # are left to whatever state before reset, so set the AC and fan relays
        # to a known state (closed)
        # XXX Should this have some hysteresis in case this is always starting
        #     and engaging the ac/fan? It already has a delay in main.py, but
        #     the main loop could have a warm-up timer where it ignores the
        #     rules (or just a plain sleep)
        log_info("Setting ac to known state")
        uart_write(uart, ac_off)
        log_info("Setting fan to known state")
        uart_write(uart, fan_off)

        client = mqtt_create(mqtt_broker, client_id, topic_root, sub_cb)
        # Now that the state is known, connect and accept control commands
        mqtt_connect(client)

        # Advertise the initial state after accepting control commands so
        # clients can start as soon as they get the initial state
        # XXX There's the theoretical possibility of a stale control command
        #     coming in before the state is advertised?
        mqtt_publish_state_message(client, state)

        gc.collect()

        log_info("Starting sensor reading and MQTT message handling forever loop")
        while (True):
            # Gather sensor information
            # Do sparingly since this can take 2s on DHT22, 1s on DHT11
            dht_sensor.measure()

            temp = dht_sensor.temperature()
            humid = dht_sensor.humidity()

            # Publish sensor information
            # XXX Should this only publish changes?
            mqtt_publish_message(client, "info/sensor", {'temp' : temp, 'humid' : humid})

            state["sensor"]["temp"] = temp
            state["sensor"]["humid"] = humid

            sync_time_with_ntp()

            # Wait some seconds between reporting sensor data (the wait could be
            # longer depending on the execution time of the loop below, but it's
            # okay even if we ever need some time accurate policy because sensor
            # messages contain timestamp)
            sleep_ms = 10_000
            # Sleep a few millis between message checks so target temperature
            # updates on the client are responsive
            sleep_iteration_ms = 500
            iterations = sleep_ms / sleep_iteration_ms
            for i in range(iterations):
                # Non-blocking check for messages

                try:
                    # This raises OSERROR (-1), ECONNRESET (errno 114),
                    # ECONNABORTED (errno 113) on error
                    mqtt_check_msg(client)

                except OSError as e:
                    # Don't bother logging these to file as they are too noisy
                    # and non fatal
                    log_exception("Exception checking MQTT message", e, True)

                    mqtt_connect(client)

                time.sleep_ms(sleep_iteration_ms)

                # Check rules
                # Collect active rules
                # No need to refresh temp, will be refreshed in the next 30s
                # XXX This only needs checking if the rules changed, since the
                #     temperature is only updated above
                ac_rules = state["config"]["ac_rules"]
                # XXX Note heating/cooling assumes the right wire (white heating
                #     / yellow for cooling) is being driven by the ac_on relay.
                #     Ideally this should use a three or four-channel relay,
                #     another option is to connect the green wire (fan) to both,
                #     drive heating with the current fan relay the and lose
                #     independent fan control?
                heating = (state["config"]["mode"] == "heating")
                cooling = not heating
                for rule in ac_rules:
                    rule_state = rule["state"]
                    rule_temp = rule.get("temp", None)
                    # XXX In heating mode the AC seems to wait for around one
                    #     minute to turn the fan off, there should be a way to
                    #     put that safety rule
                    if (rule_temp is not None):
                        under_threshold = (temp*10 <= rule_temp*10 - lo_threshold_decidegs)
                        over_threshold = (temp*10 >= rule_temp*10 + hi_threshold_decidegs)
                        if ((state["ac"] != "on") and (rule_state == "on") and
                            ((heating and under_threshold) or (cooling and over_threshold))):
                            log_info("Starting ac")
                            turn_ac(uart, client, True)

                        elif ((state["ac"] != "off") and (rule_state == "on") and
                            ((heating and over_threshold) or (cooling and under_threshold))):
                            log_info("Stopping ac")
                            turn_ac(uart, client, False)

                fan_rules = state["config"]["fan_rules"]
                for rule in fan_rules:
                    rule_state = rule["state"]
                    if ((rule_state == "on") and (state["fan"] != "on")):
                        log_info("Starting fan")
                        turn_fan(uart, client, True)

                    elif ((rule_state == "auto") and (state["fan"] != state["ac"])):
                        # Note that in reality this code will only run to turn
                        # the fan off, the fan is turned on unconditionally for
                        # safety reasons at ac turn on time
                        log_info("Matching fan to ac")
                        turn_fan(uart, client, state["ac"] == "on")

        mqtt_disconnect(client)

    finally:
        log_info("Exited main, writing configuration")
        # Write the configuration only at exit, don't want to wear out the flash
        # by writing to it on every target degree update
        # XXX This should write every day/hour if pending, otherwise when
        #     rebooting due to eg missing power it won't save
        write_config(config_filename, state)

if (__name__ == "__main__"):
    main()


import pygame, settings, random
from pygame.locals import *

inactive_color = (200, 0, 0)
active_color = (255, 0, 0)

def button(screen_surface, text, x, y, w, h, active_color, inactive_color, sound):
    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()

    if x+w > mouse[0] > x and y+h > mouse[1] > y:
        pygame.draw.rect(screen_surface, active_color, (x, y, w, h))
        if click[0]:
            settings.sound_click(sound)
            return True
    else:
        pygame.draw.rect(screen_surface, inactive_color, (x, y, w, h))

    myfont = pygame.font.Font("images/Marline.otf", 45)
    button_text = myfont.render(text, True, (255, 255, 255))
    textRect = button_text.get_rect()
    textRect.center = (x+(w/2), y+(h/2))
    screen_surface.blit(button_text, textRect)

class Player:
    def __init__(self, x, y, name):
        self.image = pygame.image.load("images/"+name+".png")

        self.pos_x = 0
        self.pos_y = 0
        self.rect = self.image.get_rect()
        self.rect.left = x
        self.rect.bottom = y

    def left(self):
        self.pos_x = -32

    def right(self):
        self.pos_x = 32

    def up(self):
        self.pos_y = -43

    def down(self):
        self.pos_y = 43

    def draw(self, screen_surface):
        screen_surface.blit(self.image, self.rect)

    def collision_x(self, plateau, screen_surface, volume):
        for obstacle in plateau.collisionList:
            if obstacle.rect.colliderect(self.rect):
                if isinstance (obstacle, Obstacle):
                    if self.pos_x > 0:
                        self.rect.right = obstacle.rect.left
                    elif self.pos_x < 0:
                        self.rect.left = obstacle.rect.right
                elif isinstance (obstacle, Player):
                    failure_screen(screen_surface, volume)
                    return False
        return True

    def collision_y(self, plateau, screen_surface, volume):
        for obstacle in plateau.collisionList:
            if obstacle.rect.colliderect(self.rect):
                if isinstance (obstacle, Obstacle):
                    if self.pos_y > 0:
                        self.rect.bottom = obstacle.rect.top
                    elif self.pos_y < 0:
                        self.rect.top = obstacle.rect.bottom
                elif isinstance (obstacle, Player):
                    failure_screen(screen_surface, volume)
                    return False
        return True

def failure_screen(screen_surface, volume):
    aff = True
    screen_fail = pygame.image.load("images/gameover.png")
    pygame.mixer.music.load("sounds/failure.mp3")
    pygame.mixer.music.set_volume(0.5 * int(volume))
    pygame.mixer.music.play(-1)
    while aff:
        screen_surface.blit(screen_fail, (0, 0))
        pygame.display.set_caption("dommage")
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                aff = False
        if button(screen_surface, "Ok", 400, 450, 70, 45, (50, 0, 0), (0, 0, 0), sound = False):
            aff = False
        pygame.display.update()

class Obstacle:
    def __init__(self, x, y, name):
        self.x = x
        self.y = y
        self.name = name
        self.image = pygame.image.load("images/cupcake.png")
        self.rect = self.image.get_rect()
        self.rect.left = (self.x)*32
        self.rect.bottom = (self.y)*43

    def draw(self, screen_surface):
        screen_surface.blit(self.image, self.rect)

class Plateau:
    def __init__(self):
        self.name = "plateau.txt"
        self.list_plein = []
        self.collisionList = []

    def read_txt(self):
        fichier = open(self.name, 'r')
        tmp = list(fichier)

        plateau = []
        for ligne in tmp:
            plateau += [list(ligne)]

        for i in range(len(plateau) - 1):
            plateau[i].pop()

        return plateau

    def create_collisionList(self, screen_surface):
        self.list_plein = self.read_txt()

        for j in range(len(self.list_plein)):
            for i in range(len(self.list_plein[j])):
                if self.list_plein[j][i] == 'p':
                    obstacle = Obstacle(i, j, 'p')
                    self.collisionList.append(obstacle)
                    obstacle.draw(screen_surface)

    def spawn_ennemy(self, screen_surface, nb_ennemy):
        ennemy = 0
        self.list_plein = self.read_txt()
        ennemy_list = []

        while ennemy != nb_ennemy:
            j = random.randint(1, len(self.list_plein)-1)
            i = random.randint(1, len(self.list_plein[0])-1)
            if self.list_plein[j][i] == 'v':
                chat_pain = Player((i * 32), (j * 43), "mechant_chat_pain_")
                self.collisionList.append(chat_pain)
                ennemy_list.append(chat_pain)
                ennemy += 1
        return ennemy_list

def ft_jouer(screen_surface, volume):
    screen_surface = pygame.display.set_mode((870,645))
    accueil_img = pygame.image.load("images/background.jpg")
    player = Player(0, 43, "boulanger_")
    player.draw(screen_surface)
    plateau = Plateau()
    ennemy_list = plateau.spawn_ennemy(screen_surface, 1)
    pygame.mixer.init()
    pygame.mixer.music.load("sounds/music.mp3")
    pygame.mixer.music.set_volume(0.25 * int(volume))
    pygame.mixer.music.play(-1)

    running_game = True
    while running_game:
        pygame.display.set_caption("maintenant, écrase les chats...")
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running_game = False
            if event.type == KEYDOWN:
                if event.key == K_LEFT:
                    player.left()
                    player.rect.x += player.pos_x
                    running_game = player.collision_x(plateau, screen_surface, volume)
                if event.key == K_RIGHT:
                    player.right()
                    player.rect.x += player.pos_x
                    running_game = player.collision_x(plateau, screen_surface, volume)
                if event.key == K_UP:
                    player.up()
                    player.rect.y += player.pos_y
                    running_game = player.collision_y(plateau, screen_surface, volume)
                if event.key == K_DOWN:
                    player.down()
                    player.rect.y += player.pos_y
                    running_game = player.collision_y(plateau, screen_surface, volume)

        screen_surface.blit(accueil_img, (0, 0))
        plateau.create_collisionList(screen_surface)
        player.draw(screen_surface)
        for i in ennemy_list:
            i.draw(screen_surface)

        if button(screen_surface, "Back", 800, 0, 70, 45, active_color, inactive_color, sound = False):
            running_game = False

        pygame.display.update()


import unittest

from abc import ABC
from typing import Iterable
from typing import List

from iamsystem.fuzzy.abbreviations import Abbreviations
from iamsystem.fuzzy.abbreviations import token_is_upper_case
from iamsystem.fuzzy.api import NormLabelAlgo
from iamsystem.fuzzy.api import SynAlgo
from iamsystem.fuzzy.api import SynType
from iamsystem.fuzzy.cache import CacheFuzzyAlgos
from iamsystem.fuzzy.cache import get_norm_label_algos_syns
from iamsystem.fuzzy.exact import ExactMatch
from iamsystem.fuzzy.norm_fun import WordNormalizer
from iamsystem.fuzzy.regex import FuzzyRegex
from iamsystem.fuzzy.spellwise import ESpellWiseAlgo
from iamsystem.fuzzy.spellwise import SpellWiseWrapper
from iamsystem.keywords.keywords import Keyword
from iamsystem.matcher.matcher import Matcher
from iamsystem.tokenization.api import TokenT
from iamsystem.tokenization.tokenize import english_tokenizer
from iamsystem.tokenization.tokenize import french_tokenizer
from iamsystem.tokenization.tokenize import split_find_iter_closure
from tests.utils import get_termino_ulceres

class TestFuzzyAlgo(NormLabelAlgo, ABC):
    """A fuzzy algos that tests how many times it is called."""

    def __init__(self, name: str = "test_fuzzy"):
        super().__init__(name)
        self.calls = 0
        self.exact = ExactMatch()

    def get_syns_of_word(self, word: str) -> Iterable[SynType]:
        """exact match and count calls."""
        self.calls += 1
        return self.exact.get_syns_of_word(word)

class InvalidSynonymsReturnedTest(unittest.TestCase):
    def test_fuzzy_algo_returns_string(self):
        """fuzzy algorithms must return a tuple of string,
        ex: ('insuffisance', 'cardiaque'). if the algorithm returns another
        iterable like a list, it must fail fast (exception raise).
        """

        def getSynonymsWrong(token: str):
            return [token]

        fuzzy = TestFuzzyAlgo()
        fuzzy.get_syns_of_word = getSynonymsWrong
        cache_syns = CacheFuzzyAlgos()
        cache_syns.add_algo(fuzzy)
        with (self.assertRaises(TypeError)):
            cache_syns.get_synonyms("anyToken")

class AbbreviationTest(unittest.TestCase):
    def get_first_token_long_forms(self, text: str) -> List[SynType]:
        """Utility function to return the long forms of the first token."""
        tokens = self.tokenizer.tokenize(text=text)
        first_token = tokens[0]
        long_forms: List[SynType] = list(
            self.abbs.get_syns_of_token(first_token)
        )
        return long_forms

    def setUp(self) -> None:
        self.abbs = Abbreviations(
            name="my abbreviations",
            token_is_an_abbreviation=token_is_upper_case,
        )
        self.tokenizer = french_tokenizer()
        self.abbs.add(
            short_form="avc",
            long_form="accident vasculaire cerebral",
            tokenizer=self.tokenizer,
        )

    def test_uppercase(self):
        """An abbreviations instance returns the long form only
        if the token is uppercase."""
        long_forms = self.get_first_token_long_forms(text="AVC sylvien")
        self.assertEqual(1, len(long_forms))

    def test_lowercase(self):
        """Lowercase so no annotation returned (upper only)."""
        long_forms = self.get_first_token_long_forms(text="avc sylvien")
        self.assertEqual(0, len(long_forms))

    def test_custom_token_is_an_abbreviation(self):
        """Modify the function that checks if a token is an abbreviation."""

        def first_letter_is_upper(token: TokenT) -> bool:
            """Return True if the first token's characters is uppercase."""
            return token.label[0].isupper()

        self.abbs.is_token_an_abbreviation = first_letter_is_upper
        long_forms = self.get_first_token_long_forms(text="aVC sylvien")
        self.assertEqual(0, len(long_forms))

        long_forms = self.get_first_token_long_forms(text="Avc sylvien")
        self.assertEqual(1, len(long_forms))

class AbbreviationsTest(unittest.TestCase):
    def setUp(self) -> None:
        self.abbs = Abbreviations(
            name="abbs", token_is_an_abbreviation=lambda token: True
        )
        self.tokenizer = french_tokenizer()

    def test_no_syn_returned(self):
        """A global empty tuple is returned by fuzzy algorithms
        when the algorithm has no suggestions"""
        self.assertEqual(len(list(self.abbs.get_syns_of_word("avc"))), 0)

    def test_add(self):
        """Add 2 long forms, check the 2 are returned."""
        self.abbs.add(
            short_form="avc",
            long_form="accident vasculaire cerebral",
            tokenizer=self.tokenizer,
        )
        long_forms: List[SynType] = list(self.abbs.get_syns_of_word("avc"))
        self.assertEqual(1, len(long_forms))
        self.assertTrue(("accident", "vasculaire", "cerebral") in long_forms)
        # another long_form :
        self.abbs.add(
            short_form="avc",
            long_form="another abbreviation",
            tokenizer=self.tokenizer,
        )
        long_forms: Iterable[SynType] = self.abbs.get_syns_of_word("avc")
        self.assertEqual(2, len(list(long_forms)))

    def test_add_tokenized_long_form(self):
        """Test adding a sequence of words."""
        self.abbs.add_tokenized_long_form(
            short_form="avc",
            long_form=["accident", "vasculaire", "cerebral"],
        )
        long_forms: List[SynType] = list(self.abbs.get_syns_of_word("avc"))
        self.assertEqual(1, len(long_forms))
        self.assertTrue(("accident", "vasculaire", "cerebral") in long_forms)

    def test_add_lower_short_form(self):
        """Even though the given short form is uppercase,
        a lowercase word returns the long form."""
        self.abbs.add(
            short_form="AVC",
            long_form="accident vasculaire cerebral",
            tokenizer=self.tokenizer,
        )
        long_forms: List[SynType] = list(self.abbs.get_syns_of_word("avc"))
        self.assertEqual(len(long_forms), 1)

class TupleTest(unittest.TestCase):
    def test_tuple_stored_in_set(self):
        """check a tuple added twice in a set is ignored (default Python)"""
        syns_set = set()
        syn1 = tuple(["insuffisance", "respiratoire", "chronique"])
        syn2 = tuple(["insuffisance", "renale", "chronique"])
        syns_set.add(syn1)
        syns_set.add(syn2)
        self.assertEqual(2, len(syns_set))
        syns_set.add(syn2)
        self.assertEqual(2, len(syns_set))

    def test_tuple_list_vs_tuple_string(self):
        """One frequent mistake I make and that users can make is to call
        'tuple' function on string and not on a list.
        Calling it on a string returns a tuple of characters.
        """
        syns_set = set()
        syn1 = tuple(["insuffisance"])  # ('insuffisance')
        syn2 = tuple("insuffisance")  # ('i','n',...,'e')
        syns_set.add(syn1)
        syns_set.add(syn2)
        self.assertEqual(2, len(syns_set))

class ExactMatchTest(unittest.TestCase):
    def test_exact_match(self):
        """Test exact match returns the correct format."""
        exact = ExactMatch()
        syns = exact.get_syns_of_word("insuffisance")
        self.assertTrue(tuple(["insuffisance"]) in syns)

class MultipleFuzzyAlgosTest(unittest.TestCase):
    def setUp(self) -> None:
        abbs = Abbreviations(name="abbs")
        tokenizer = english_tokenizer()
        abbs.add(
            short_form="fr",
            long_form="frequence respiratoire",
            tokenizer=tokenizer,
        )
        exact_match = ExactMatch()
        self.fuzzy_algos = [exact_match, abbs]
        self.tokens = tokenizer.tokenize("augmentation de la fr")
        self.i = 3  # 'fr' token

    def test_get_norm_label_algos_syns(self):
        """Return all the synonyms of NormLabel algorithms."""
        syns: List[SynAlgo] = get_norm_label_algos_syns(
            fuzzy_algos=self.fuzzy_algos, norm_label="fr"
        )
        self.assertEqual(len(syns), 2)
        label, algo = syns[0]
        self.assertEqual(tuple(["fr"]), label)
        label, algo = syns[1]
        self.assertEqual(tuple(["frequence", "respiratoire"]), label)

class CacheFuzzyAlgosTest(unittest.TestCase):
    def setUp(self) -> None:
        self.fuzzy = TestFuzzyAlgo()
        self.cache_syns = CacheFuzzyAlgos()
        self.tuple_ins = tuple(["insuffisance"])

    def test_number_of_calls(self):
        """The purpose of the cache is to call fuzzy algorithms only once and
        then stores the results in cache. This test is just to check that
        'calls' attribute record correctly the number of calls.
        """
        self.fuzzy.get_syns_of_word("insuffisance")
        self.fuzzy.get_syns_of_word("insuffisance")
        self.assertEqual(2, self.fuzzy.calls)

    def test_caching(self):
        """Fuzzy algorithms cached are only called once and cache
        must work (synonyms are returned)."""
        self.cache_syns.add_algo(self.fuzzy)
        self.assertEqual(0, self.fuzzy.calls)
        self.cache_syns.get_syns_of_word("insuffisance")
        self.assertEqual(1, self.fuzzy.calls)  # test_fuzzy was called
        self.cache_syns.get_syns_of_word("insuffisance")
        self.assertEqual(
            1, self.fuzzy.calls
        )  # test_fuzzy was not called twice
        self.assertTrue("insuffisance" in self.cache_syns.cache)

    def test_get_syns_of_word(self):
        """Add exact match algorithm and check it returns a synonym."""
        self.cache_syns.add_algo(algo=ExactMatch())
        syns: List[SynAlgo] = self.cache_syns.get_syns_of_word("insuffisance")
        syn: SynAlgo = syns[0]
        synonym, algo = syn
        self.assertEqual(self.tuple_ins, synonym)
        self.assertEqual(algo, "exact")

    def test_get_syns_of_word_no_algo(self):
        """By default the cache contains no algorithm so it returns no
        synonym."""
        syns = self.cache_syns.get_syns_of_word("insuffisance")
        self.assertEqual(0, len(syns))

    def test_add_spellwise(self):
        """Check it call the spellwise algorithm and stores its suggestions"""
        algo: SpellWiseWrapper = SpellWiseWrapper(
            ESpellWiseAlgo.LEVENSHTEIN, max_distance=1
        )
        algo.add_words(words=["insuffisance"])
        self.cache_syns.add_algo(algo)
        syns = self.cache_syns.get_syns_of_word("insufisance")
        syn = syns[0]
        self.assertTrue(self.tuple_ins in syn)

class FuzzyRegexTest(unittest.TestCase):
    def setUp(self) -> None:
        self.fuzzy = FuzzyRegex(
            name="regex_num",
            pattern=r"^\d*[.,]?\d*$",
            pattern_name="numval",
        )
        split = split_find_iter_closure(pattern=r"(\w|\.|,)+")
        self.tokenizer = french_tokenizer()
        self.tokenizer.split = split
        self.matcher = Matcher(tokenizer=self.tokenizer)

    def test_split_function(self):
        """[calcium, 2.1, mmol, L] expected."""
        tokens = self.tokenizer.tokenize("calcium 2.1 mmol/L")
        self.assertEqual(4, len(tokens))

    def test_replace_pattern_in_keyword(self):
        """The function replaces regex matches by the pattern_name
        In this example, span containing '2.1' is replaced by 'numval'
        """
        keyword = Keyword(label="calcium 2.1 mmol/L")
        new_keyword = self.fuzzy.replace_pattern_in_keyword(
            keyword=keyword, tokenizer=self.tokenizer
        )
        self.assertEqual("calcium numval mmol/L", new_keyword.label)

    def test_get_syns_of_word(self):
        """the function should return the patter name (numval here)."""
        syns = self.fuzzy.get_syns_of_word("2.1")
        self.assertTrue(tuple(["numval"]) in syns)

    def test_detection(self):
        """It works with a matcher."""
        keyword = Keyword(label="CALCIUM NUMVAL mmol/L")
        self.matcher.add_keywords(keywords=[keyword])
        annots = self.matcher.annot_text(text="calcium 2.1 mmol/L")
        self.assertEqual(0, len(annots))
        self.matcher.add_fuzzy_algo(self.fuzzy)
        annots = self.matcher.annot_text(text="calcium 2.1 mmol/L")
        self.assertEqual(1, len(annots))

class WordNormalizerTest(unittest.TestCase):
    def simple_stemmer(self, string: str):
        if string.startswith("gastr"):
            return "gastr"
        else:
            return string

    def setUp(self) -> None:
        self.fuzzy_stemmer = WordNormalizer(
            name="stemmer", norm_fun=self.simple_stemmer
        )
        self.tokenizer = french_tokenizer()
        self.detector = Matcher(tokenizer=self.tokenizer)
        self.detector.add_fuzzy_algo(self.fuzzy_stemmer)

    def test_get_syns_of_word(self):
        """'gastrique' and 'gastrologique' start with 'gastr'
        so 'gastrique' is a synonym of 'gastrologique'.
        """
        self.fuzzy_stemmer.add_words(words=["gastrique", "gastriques"])
        synonyms = self.fuzzy_stemmer.get_syns_of_word("gastrologique")
        self.assertTrue(tuple(["gastrique"]) in synonyms)

    def test_detection_stem(self):
        """'gastrique' and 'gastrologique' start with 'gastr'
        Matcher matches it.
        """
        self.fuzzy_stemmer.add_words(words=["gastrique"])
        self.detector.add_keywords(keywords=get_termino_ulceres())
        text = "Ulcère gastrologique"
        annots = self.detector.annot_text(text)
        self.assertEqual(1, len(annots))

if __name__ == "__main__":
    unittest.main()


import pandas as pd

df = pd.read_csv('titanic_raw.csv')

df.head()

df.describe()

df.dtypes

df.nunique

df.corr(df)['survived']

df.corr()['survivied'].sort_values(ascending=False)
df = df.loc[:,['pclass', 'survived','age', 'sex','embarked']]
df.isna().sum()
df.age.fillna(df.age.median(), inplace=True)
df = df.loc[df.embarked.notna(), :]

calculate_bonus = lambda sales:sales*0.02 if sales > 50000 else sales*0.01
print(calculate_bonus(sales=600))
print(calculate_bonus(600))

#Part 1

import pandas as pd

df = pd.read_csv('titanic_raw.csv')

print('Dataset Shape:', df.shape)

print('\nFirst 5 Rows:')
print(df.head())

print('\nData Types:')
print(df.dtypes)

print('\nMissing Values:')
print(df.isna().sum())

print('\nCategorical Variables:')
for col in ['survived', 'pclass', 'sex', 'embarked']:
    print('\n', col)
    print(df[col].value_counts())

print('\nNumerical Variables:')
print(df.describe())

print('\nCorrelation Matrix:')
corr = df.corr()
print(corr)

#Part 2
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix

df = pd.read_csv("titanic_raw.csv")

X = df.drop("survived", axis='columns')
y = df["survived"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y)


#!/usr/bin/env python3

import urllib.request
import csv
import re
from datetime import datetime

# Define the URLs to scrape
broad_url = "https://aukro.cz/vysledky-vyhledavani?sort=endingTime_ASC&text=1000&categoryId=109883"
narrow_url = "https://aukro.cz/lp/bankovky-1000-kc-cnb-30-let-menove-odluky?sort=endingTime:ASC"

# Set the user agent to a regular browser
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'

# Make a GET request to the broad search URL
broad_request = urllib.request.Request(broad_url, headers={'User-Agent': user_agent})
with urllib.request.urlopen(broad_request) as response:
    broad_html = response.read().decode('utf-8')

# Extract the broad count value from the HTML (one value)
broad_count_start_index = broad_html.find('')
broad_count_end_index = broad_html.find('', broad_count_start_index)
broad_count = broad_html[broad_count_start_index:broad_count_end_index].split('>')[-1].replace('\xa0', '')

# remove non-numeric characters
broad_count = ''.join(filter(str.isdigit, broad_count))

# Extract the values of class "buy-price" from the HTML, including the time left in minutes
broad_prices = re.findall(r'(?<=přihazuj).*?(\d[\d\s]*)\s*Kč\s*(?=.*?min)(?=.*?(?:méně než minuta|\d{1,2} min))', broad_html)
broad_prices = [int(re.sub(r'\s', '', price)) for price in broad_prices]

# Get the count of the prices (should be around 14 of them)
broad_price_count = len(broad_prices)

# Make a GET request to the narrow search URL
narrow_request = urllib.request.Request(narrow_url, headers={'User-Agent': user_agent})
with urllib.request.urlopen(narrow_request) as response:
    narrow_html = response.read().decode('utf-8')

# Extract the narrow count value from the HTML
narrow_count_start_index = narrow_html.find('')
narrow_count_end_index = narrow_html.find('', narrow_count_start_index)
narrow_count = narrow_html[narrow_count_start_index:narrow_count_end_index].split('>')[-1].replace('\xa0', '')

# remove non-numeric characters
narrow_count = ''.join(filter(str.isdigit, narrow_count))

# Extract the values of class "buy-price" from the HTML, including the time left in minutes
narrow_prices = re.findall(r'(?<=přihazuj).*?(\d[\d\s]*)\s*Kč\s*(?=.*?min)(?=.*?(?:méně než minuta|\d{1,2} min))', narrow_html)
narrow_prices = [int(re.sub(r'\s', '', price)) for price in narrow_prices]

# Get the count of the prices
narrow_price_count = len(narrow_prices)

# Get the current timestamp in the desired format
timestamp = datetime.now().strftime('%d/%m/%y %H:%M')

# Calculate the average and median of the broad and narrow prices
broad_average = sum(broad_prices) / len(broad_prices) if broad_prices else 0
broad_median = sorted(broad_prices)[len(broad_prices) // 2] if broad_prices else 0
narrow_average = sum(narrow_prices) / len(narrow_prices) if narrow_prices else 0
narrow_median = sorted(narrow_prices)[len(narrow_prices) // 2] if narrow_prices else 0

# Set up the CSV dialect with semicolon delimiter
csv.register_dialect('mydialect', delimiter=';', quoting=csv.QUOTE_MINIMAL)

# Write the data to a CSV file
with open('/volume1/SDILENE/AUKRO1000CZKCNB/search_results.csv', 'a', newline='') as f:
    writer = csv.writer(f, dialect='mydialect')
    writer.writerow([
        timestamp,
        broad_count,
        broad_price_count,
        str(broad_average).replace('.', ','),
        str(broad_median).replace('.', ','),
        narrow_count,
        narrow_price_count,
        str(narrow_average).replace('.', ','),
        str(narrow_median).replace('.', ',')
    ])


from django.urls import path
from . import views

app_name = 'products'

urlpatterns = [
    path('', views.ProductListView.as_view(), name='products'),
    path('<int:pk>/', views.ProductDetaiView.as_view(), name='product_detail'),
    path(
        'categories/<int:pk>/',
        views.CategoryListView.as_view(),
        name='category_list'
        ),
    path('tags/<str:pk>/', views.TagListView.as_view(), name='tag_list'),
    path('add/', views.add_product, name='add_product'),
    path('edit/<int:product_id>/', views.edit_product, name='edit_product'),
    path(
        'delete/<int:product_id>/',
        views.delete_product, name='delete_product'
        ),
]


from time import sleep
from playsound import playsound

CLEAR = "\033[2J"
CLEAR_AND_RETURN = "\033[H"

def timer(x):
    time_elapsed = 0

    print(CLEAR)
    while time_elapsed < x:
        sleep(1)
        time_elapsed += 1
        time_left = x - time_elapsed
        minutes = time_left // 60
        seconds = time_left % 60

        print(f"{CLEAR_AND_RETURN}{minutes:02d}:{seconds:02d}")

    playsound("Ringtone Of Your Choice")

timer(3)


# -*- coding: utf-8 -*-
import collections

def dicts_are_equal(dict1, dict2):
    if len(set(dict1.keys()).symmetric_difference(set(dict2.keys()))) > 0:
        return False
    result = True
    for key in dict1.keys():
        if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
            result &= dicts_are_equal(dict1[key], dict2[key])
        else:
            result &= dict1[key] == dict2[key]
    return result

def recursive_dict_update(dict1, dict2):
    for key, value in dict2.items():
        dict1_entry = dict1.get(key, {})
        if isinstance(value, collections.Mapping) and isinstance(dict1_entry, collections.Mapping):
            r = recursive_dict_update(dict1_entry, value)
            dict1[key] = r
        else:
            dict1[key] = dict2[key]
    return dict1

def add_to_dict_with_path(destination_dict, key, value, path=None):
    current_level = destination_dict
    if path is not None and len(path) > 0:
        for entry in path:
            if entry not in current_level or not isinstance(current_level[entry], dict):
                current_level[entry] = {}
            current_level = current_level[entry]
    if key != "" and value != "":
        current_level[key] = value

def get_from_dict_with_path(source_dict, key, path=None, default=None):
    current_level = source_dict
    if path is not None and len(path) > 0:
        for entry in path:
            if not isinstance(current_level, collections.Mapping) or entry not in current_level:
                return default
            current_level = current_level[entry]
    if not isinstance(current_level, collections.Mapping):
        return default
    return current_level.get(key, default)

def merge_fragment_edge_data(fragment_data_1, fragment_data_2):
    result = {"name": [fragment_data_1["name"] if fragment_data_1 is not None else None,
                       fragment_data_2["name"] if fragment_data_2 is not None else None],
              "forward_orientation": [fragment_data_1["forward_orientation"] if fragment_data_1 is not None else None,
                                      fragment_data_2["forward_orientation"] if fragment_data_2 is not None else None]}
    return result


import pandas as pd
from sklearn.preprocessing import StandardScaler
import seaborn as sns

data = sns.load_dataset('iris')
print('Original Dataset')
data.head()

# Remove the Non-numeric Column
df=data.drop('species', axis=1)

std_scaler = StandardScaler()

df_scaled = std_scaler.fit_transform(df.to_numpy())
df_scaled = pd.DataFrame(df_scaled, columns=[
    'sepal_length', 'sepal_width', 'petal_length', 'petal_width'])

print("Scaled Dataset Using StandardScaler")
print(df_scaled.head())


"""pulpapi - Access to Pulp REST API"""
from rest_framework.response import Response
from rest_framework.reverse import reverse as reverse_absolute
from rest_framework import renderers
from rest_framework import serializers
from rest_framework import status
from rest_framework import viewsets
import requests

#TODO: Configurable Pulp connectivity. For now, assume a local container
# service link called "pulpapi", and don't verify the HTTPS connection

#=====================================
# Client access to the remote Pulp API
#=====================================

pulp_url = "https://pulpapi/pulp"
pulp_api_path = "/api/v2/"
pulp_api_url = pulp_url + pulp_api_path
pulp_repo_url = "http://localhost/pulp/repos/"

def _access_pulp_url(http_method, *api_segments, **request_kwds):
    url_segments = [pulp_api_url]
    url_segments.extend(api_segments)
    api_url = "/".join(url_segments) + "/"
    reply = http_method(api_url,
                        auth=('admin', 'admin'),
                        verify=False,
                        **request_kwds)
    reply.raise_for_status()
    return reply

def _get_pulp_url(*api_segments, **request_kwds):
    return _access_pulp_url(requests.get, *api_segments, **request_kwds)

def _post_pulp_url(*api_segments, **request_kwds):
    return _access_pulp_url(requests.post, *api_segments, **request_kwds)

def _delete_pulp_url(*api_segments, **request_kwds):
    return _access_pulp_url(requests.delete, *api_segments, **request_kwds)

def _convert_repo(repo):
    repo_id = repo["id"]
    return {"repo_id": repo_id,
            "display_name": repo["display_name"],
            "details": repo,
           }

def iter_repos():
    pulp_info = _get_pulp_url("repositories")
    if pulp_info.text == "not found":
        raw_repos = []
    else:
        raw_repos = pulp_info.json()
    for repo in raw_repos:
        yield _convert_repo(repo)

def get_repo(repo_id):
    return _convert_repo(_get_pulp_url("repositories", repo_id).json())

def create_repo(repo_id, display_name):
    details = {"id":repo_id, "display_name":display_name}
    pulp_reply = _post_pulp_url("repositories", json=details)
    return _convert_repo(pulp_reply.json())

def delete_repo(repo_id):
    try:
        return _delete_pulp_url("repositories", repo_id).json()
    except requests.HTTPError as e:
        if e.response.status_code == status.HTTP_404_NOT_FOUND:
            return {"error": None, "result": None, "spawned_tasks": []}
        raise

def set_feed(repo_id, feed_url=None):
    config = {"importer_type_id": "yum_importer"}
    if feed_url is not None:
        config["importer_config"] = {"feed": feed_url}
    pulp_reply = _post_pulp_url("repositories", repo_id, "importers",
                                json=config)
    return pulp_reply.json()

def get_feed(repo_id):
    pulp_reply = _get_pulp_url("repositories", repo_id, "importers")
    return pulp_reply.json()[0]

def start_sync(repo_id):
    pulp_reply = _post_pulp_url("repositories", repo_id, "actions/sync")
    return pulp_reply.json()

def start_merge(source_repo_id, target_repo_id):
    config = {"source_repo_id": source_repo_id}
    pulp_reply = _post_pulp_url("repositories", target_repo_id,
                                "actions/associate", json=config)
    return pulp_reply.json()

def set_target(repo_id):
    config = {
        "distributor_id": "target",
        "distributor_type_id": "yum_distributor",
        "distributor_config": {
            "http": True,
            "https": False, # No default SSL certs set up
            "relative_url": repo_id,
        },
        "auto_publish": True
    }
    pulp_reply = _post_pulp_url("repositories", repo_id, "distributors",
                                json=config)
    distributor = pulp_reply.json()
    repo_url = pulp_repo_url + distributor["config"]["relative_url"]
    return distributor, repo_url

def get_target(repo_id):
    pulp_reply = _get_pulp_url("repositories", repo_id, "distributors")
    return pulp_reply.json()[0]

def start_publish(repo_id):
    config = {"id": "target"}
    pulp_reply = _post_pulp_url("repositories", repo_id, "actions/publish",
                                json=config)
    return pulp_reply.json()

# TODO: Pass through properly structured error information
class RemotePulpError(Exception): pass

# TODO: Switch from requests to aiohttp and make this non-blocking
def wait_for_task(task_id):
    while True:
        pulp_reply = _get_pulp_url("tasks", task_id)
        reply_data = pulp_reply.json()
        status = reply_data["state"]
        if status == "finished":
            break
        if status not in ("waiting", "running", "suspended"):
            raise RemotePulpError(str(reply_data))
    return reply_data

#=========================================
# Local REST API proxy for remote Pulp API
#=========================================

COLLECTION_NAME = 'pulp_repo'
def add_to_router(router, prefix):
    router.register(prefix, PulpRepoViewSet, base_name=COLLECTION_NAME)

# Adapts HyperlinkedIdentityField to use __getitem__ rather than __getattr__
class DictBasedIdentityField(serializers.HyperlinkedIdentityField):

    def get_url(self, obj, view_name, request, format):
        # Unsaved objects will not yet have a valid URL.
        try:
            lookup_value = obj[self.lookup_field]
        except KeyError:
            return None
        kwargs = {self.lookup_url_kwarg: lookup_value}
        return self.reverse(view_name, kwargs=kwargs, request=request, format=format)

class PulpRepoSerializer(serializers.Serializer):
    url = DictBasedIdentityField(view_name='pulp_repo-detail',
                                 lookup_field='repo_id',
                                 lookup_url_kwarg='pk')
    repo_id = serializers.CharField()
    display_name = serializers.CharField()
    details = serializers.DictField(read_only=True)

    def create(self, validated_data):
        repo = pulpapi.create_repo(**validated_data)
        return repo

class PulpRepoViewSet(viewsets.ViewSet):
    serializer_class = PulpRepoSerializer

    def _serialize(self, data, request, *, many=False):
        context={'request': request}
        return self.serializer_class(data, context=context, many=many).data

    def list(self, request):
        return Response(self._serialize(iter_repos(), request, many=True))

    def retrieve(self, request, *, pk):
        return Response(self._serialize(get_repo(pk), request))

    def create(self, request):
        serializer = self.serializer_class(data=request.data)
        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
        repo = serializer.save()
        return Response(self._serialize(repo, request),
                        status=status.HTTP_201_CREATED)


#!/raid-manaslu/k2-raid/traxl/anaconda3/envs/py3/bin/python

import os
import argparse
import itertools
from datetime import datetime

import numpy as np
import pandas as pd
pd.options.mode.chained_assignment = None

# argument parameters
parser = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
    '-l', '--locations',
    nargs='+',
    help='list of locations to compute bursts for',
    required=True)
args = parser.parse_args()

# parameters
r = .1
p = 0

seasons = ['JJA', 'DJF', 'MJJAS', 'NDJFM']
tds = list(range(0, 49))
tds = tds[::-1]

# filesystem folders
storefolder = os.getcwd() + '/'
subfolder = 'v_r{}_p{}_SXT_td_X/'.format(r, p)
v_table = 'v_r{}_p{}_dtime_l_r_seasons_l_sortedc7.h5'.format(r, p)
v_burst_table = 'v_r{}_p{}_bursts_l_sortedc7.h5'.format(r, p)
partsfolder = 'glcp_burst_parts/'
os.makedirs(storefolder + partsfolder, exist_ok=True)

def compute_burst(location):

    print('running {} {}'.format(location, datetime.now()))

    timer_1 = datetime.now()

    # load dtime, l, r, seasons
    v = pd.read_hdf(
        storefolder + v_table, 'v',
        where='l == {}'.format(location),
        # columns=['dtime', 'l', 'r'],
    )

    # load burst
    v['cp_burst'] = pd.read_hdf(
        storefolder + v_burst_table, 'v',
        where='l == {}'.format(location),
        columns=['cp_burst'])

    # load sxt_td_x
    for which in ['sat', 'sdt']:
        for td in tds:
            v_sxt_table = '{}_td_{}_l_sortedc7.h5'.format(which, td)
            v['{}_td_{}'.format(which, td)] = pd.read_hdf(
                storefolder + subfolder + v_sxt_table, 'v',
                where='l == {}'.format(location),
                columns=['{}_td_{}'.format(which, td)])

    gv = v.groupby(['l', 'cp_burst'])
    burst_parts = []

    # compute bursts dataframe
    n_groups = len(gv)
    print('n_bursts {:05d}'.format(n_groups))
    c = 1
    for name, vt in gv:

        vt0 = vt.iloc[0]

        # measure performance
        # time_start = datetime.now()

        # column index tuples, values
        cindex = []
        values = []

        # l, cp_burst
        cindex.append(['l', ''])
        values.append(name[0])
        cindex.append(['cp_burst', ''])
        values.append(name[1])

        # dtime
        cindex.append(['dtime', ''])
        values.append(vt0['dtime'])

        # season
        for season in seasons:
            cindex.append(['season', season])
            values.append(vt0[season])

        # k-burst
        cindex.append(['k_burst', ''])
        values.append(len(vt))

        # mean r
        cindex.append(['r_mean', ''])
        values.append(vt['r'].mean())

        # max r
        cindex.append(['r_max', ''])
        values.append(vt['r'].max())

        # rainfall before the burst being analyzed
        vlb = v.loc[
            (v['dtime'] >= vt0['dtime'] - pd.Timedelta(max(tds), 'h')) &
            (v['dtime'] < vt0['dtime']), ['dtime', 'r']]
        vlb['td'] = (vlb['dtime'] - vt0['dtime'])
        vlb['td'] = vlb['td'].astype('timedelta64[h]')

        # rainfall rates
        vt['td'] = np.arange(0, len(vt)*3, 3)
        rr = pd.concat((vlb[['td', 'r']], vt[['td', 'r']]))

        cindex += (list(itertools.product(['r'], rr['td'].astype(np.int64))))
        values += rr['r'].values.tolist()

        # time reference for sat/sdt/RH
        t_ref = [-td for td in tds] + list(range(1, (len(vt) - 1) * 3 + 1))

        # sat
        sat_t0 = vt[['sat_td_{}'.format(td) for td in tds]].iloc[0].values
        sat_tplus = vt[
            ['sat_td_{}'.format(td) for td in [2, 1, 0]]
        ].iloc[1:].values.flatten()
        sat = np.concatenate((sat_t0, sat_tplus))

        cindex += (list(itertools.product(['sat'], t_ref)))
        values += sat.tolist()

        # sdt
        sdt_t0 = vt[['sdt_td_{}'.format(td) for td in tds]].iloc[0].values
        sdt_tplus = vt[
            ['sdt_td_{}'.format(td) for td in [2, 1, 0]]
        ].iloc[1:].values.flatten()
        sdt = np.concatenate((sdt_t0, sdt_tplus))

        cindex += (list(itertools.product(['sdt'], t_ref)))
        values += sdt.tolist()

        # RH
        # https://www.theweatherprediction.com/habyhints/186/
        # https://iridl.ldeo.columbia.edu/dochelp/QA/Basic/dewpoint.html
        T = sat
        T_d = sdt
        T += 273.15
        T_d += 273.15
        E_0 = 0.611
        T_0 = 273.15
        E = E_0 * np.exp(5423 * (1/T_0 - 1/T_d))
        E_s = E_0 * np.exp(5423 * (1/T_0 - 1/T))
        rh = 100 * E/E_s

        cindex += (list(itertools.product(['RH'], t_ref)))
        values += rh.tolist()

        # create dataframe
        burst = pd.DataFrame(values).T
        burst.columns = pd.MultiIndex.from_tuples(cindex)

        # set dtypes
        exclusion_list = ['l', 'cp_burst', 'k_burst', 'dtime', 'season']

        burst['l'] = burst['l'].astype(np.uint32)
        burst['cp_burst'] = burst['cp_burst'].astype(np.uint16)
        burst['k_burst'] = burst['k_burst'].astype(np.uint8)
        for season in seasons:
            burst['season', season] = burst['season', season].astype(bool)

        for col in burst:
            if not col[0] in exclusion_list:
                burst[col] = burst[col].astype(np.float32)

        # append
        burst_parts.append(burst)

        # print progress
#         timediff = datetime.now() - time_start
#         pprogress = '{:05d} / {:05d}'.format(c, n_groups)
#         ptime = ' | comp. time: s={}\t ms={}'.format(
#             int(timediff.total_seconds()),
#             str(timediff.microseconds / 1000.)[:6])
#         print(pprogress + ptime)

        c += 1

    v_burst = pd.concat(burst_parts, axis=0, sort=False)

    # set index
    v_burst.index = range(len(v_burst))

    # store as pickle
    fname = '{:06d}.pickle'.format(location)
    v_burst.to_pickle(storefolder + partsfolder + fname)

    # timer
    dt_1 = datetime.now() - timer_1
    ptime = 'comp. time s={}\t ms={}'.format(
        int(dt_1.total_seconds()),
        str(dt_1.microseconds / 1000.)[:6])
    print(ptime)

for location in args.locations:
    location = int(location)
    compute_burst(location)


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

app = Flask(__name__)

def calculate_amortization(principal, interest_rate, time):
    rate = interest_rate / 100
    monthly_interest_rate = rate / 12
    months = time * 12

    # 월별 이자 지급 계산
    monthly_payment = principal * (monthly_interest_rate / (1 - math.pow(1 + monthly_interest_rate, -months)))

    # 상환 계획
    amortization_schedule = []
    for i in range(months):
        interest_payment = principal * monthly_interest_rate
        principal_payment = monthly_payment - interest_payment
        principal -= principal_payment
        amortization_schedule.append((i + 1, monthly_payment, principal_payment, interest_payment))

    return amortization_schedule

def recommend_products():
    products = [
        {'bank': '시중은행1', 'name': '상품A', 'interest_rate': 3.9},
        {'bank': '시중은행2', 'name': '상품B', 'interest_rate': 4.5},
        {'bank': '시중은행3', 'name': '상품C', 'interest_rate': 4.8},
        {'bank': '시중은행4', 'name': '상품D', 'interest_rate': 5.1},
        {'bank': '시중은행5', 'name': '상품E', 'interest_rate': 5.6},
    ]
    return products

'''대출 계산기'''
@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        principal = request.form["principal"]
        interest_rate = request.form["interest_rate"]
        time = request.form["time"]

        return redirect(url_for("result", principal=principal, interest_rate=interest_rate, time=time))

    recommended_products = recommend_products()
    return render_template("index.html", products=recommended_products)

@app.route("/result", methods=["GET"])
def result():
    principal = float(request.args.get("principal"))
    interest_rate = float(request.args.get("interest_rate"))
    time = int(request.args.get("time"))

    amortization_schedule = calculate_amortization(principal, interest_rate, time)
    recommended_products = recommend_products()

    return render_template("index.html", schedule=amortization_schedule, products=recommended_products,
                           principal=principal, interest_rate=interest_rate, time=time)

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


import telebot
from config import currency, TOKEN
from extensions import APIException, CurrencyConverter

bot = telebot.TeleBot(TOKEN)

@bot.message_handler(commands=['start', 'help'])
def help(message: telebot.types.Message):
    text = 'Для начала нашей работы необходимо ввести информацию в следующем формате: \n <Название валюты> \
<В какую валюту необходимо перевести> \
<Количество переводимой валюты>\n Отобразить список всех доступный валют - /values'
    bot.reply_to(message, text)

@bot.message_handler(commands=['values'])
def values(message: telebot.types.Message):
    text = 'Доступные типы валют: '
    for key in currency.keys():
        text = '\n'.join((text, key,))
    bot.reply_to(message, text)

@bot.message_handler(content_types=['text', ])
def convert(message: telebot.types.Message):
    try:
        values = message.text.split(' ')
        if len(values) != 3:
            raise APIException('Указано неверное количество параметров')

        quote, base, amount = values
        total_base = CurrencyConverter.get_price(quote, base, amount)
    except APIException as e:
        bot.reply_to(message, f'Ошибка пользователя {e}')

    except Exception as e:
        bot.reply_to(message, f'Не удается обработать команду\n{e}')
    else:
        text = f'Стоимость {amount} {quote} в {base} - {total_base}'
        bot.send_message(message.chat.id, text)

bot.polling()


'''
Fourier Transforms of Simple Functions
Calculate the coefficients of the sample periodic functions at N = 1000 evenly spaced points.
plot their amplitueds

(a) A single cycle of a square wave with amplitude 1

(b) The sawtooth wave yn = n

(c) The modulated wave yn = sin(pin/N)sin(20pin/N)
'''
import numpy as np
from numpy import sin
import matplotlib.pyplot as plt
from cmath import exp, pi
from math import floor
# Making x inputs for waves
x_sqtest = np.linspace(0,3,1000)
x_stest = np.linspace(0,10,1000)
x_sin_t = np.linspace(0,1000,1000)

# Making the data points for input into the DFT
def Square_Wave(x):
    y = np.empty(len(x))
    for x,l in enumerate(x):
        if l <= 1:
            y[x] = 1
        if l > 1 and l <= 2:
            y[x] = 0
        if l > 2 and l <=3:
            y[x] =  1
    return y
def SawTooth(x):
    y = np.empty(len(x))
    for l in range(len(x)):
        y[l] = x[l] - floor(x[l])
    return y

def ModSinWave(x):
    N = len(x)
    return sin(pi*x/N)*sin(20*pi*x/N)

# Making the Discrete transform function

def Discrete_Transform(y_data):
    N = len(y_data)
    ck = 0.0
    it_thru = N//2 + 1
    c = np.empty(it_thru, complex)

    for k in range(it_thru):
        for n in range(N):
            c[k] += y_data[n]*exp(-2j*pi*k*n/N)
    return c

# Assigning the y values for each function
ysquare = Square_Wave(x_sqtest)
ysaw = SawTooth(x_stest)
ymod = ModSinWave(x_sin_t)
# Assinging the variables for the coefficients
ysqT = np.real(Discrete_Transform(ysquare))
ysT  = np.real(Discrete_Transform(ysaw))
ymT  = np.real(Discrete_Transform(ymod))
# Assigning the plotting of f spaces - [THIS IS A PROBLEM - I'M mapping onto the same spaces as before not the Frequency]
xsqDFT = np.linspace(0,100,501)
xsDFT  = np.linspace(0,100,501)
xmDFT  = np.linspace(0,100,501)

# Plotting the Functions with their appropriate DFTs
plt.figure('Square Wave')
plt.plot(x_sqtest,ysquare)
plt.figure('Square Wave - Fouier Tranform Coef')
plt.plot(xsqDFT,abs(ysqT))

plt.figure('Saw Wave')
plt.plot(x_stest,np.real(ysaw))
plt.figure('Saw Wave - Fourier Transform Coef')
plt.plot(xsqDFT,abs(ysT))

plt.figure('Mod Wave ')
plt.plot(x_sin_t,ymod)
plt.figure('Mod Wave - Fourier Transform Coef')
plt.plot(xmDFT,abs(ymT))

plt.show()