from __future__ import absolute_import, division, print_function

import numbers
from abc import ABCMeta, abstractmethod
from collections import OrderedDict, defaultdict

from six import add_metaclass

import torch
import pyro.poutine as poutine
from pyro.distributions import Categorical, Empirical
from pyro.ops.stats import waic
from pyro.infer.util import site_is_subsample
from pyro.infer import *

class TracePredictive(TracePosterior):
    """
    Generates and holds traces from the posterior predictive distribution,
    given model execution traces from the approximate posterior. This is
    achieved by constraining latent sites to randomly sampled parameter
    values from the model execution traces and running the model forward
    to generate traces with new response ("_RETURN") sites.
    :param model: arbitrary Python callable containing Pyro primitives.
    :param TracePosterior posterior: trace posterior instance holding samples from the model's approximate posterior.
    :param int num_samples: number of samples to generate.
    :param keep_sites: The sites which should be sampled from posterior distribution (default: all)
    """
    def __init__(self, model, posterior, num_samples, keep_sites=None):
        self.model = model
        self.posterior = posterior
        self.num_samples = num_samples
        self.keep_sites = keep_sites
        super(TracePredictive, self).__init__()

    def _traces(self, *args, **kwargs):
        if not self.posterior.exec_traces:
            self.posterior.run(*args, **kwargs)
        data_trace = poutine.trace(self.model).get_trace(*args, **kwargs)
        for _ in range(self.num_samples):
            model_trace = self.posterior().copy()
            self._remove_dropped_nodes(model_trace)
            #self._adjust_to_data(model_trace, data_trace)
            resampled_trace = poutine.trace(poutine.replay(self.model, model_trace)).get_trace(*args, **kwargs)
            yield (resampled_trace, 0., 0)

    def _remove_dropped_nodes(self, trace):
        if self.keep_sites is None:
            return
        for name, site in list(trace.nodes.items()):
            if name not in self.keep_sites:
                trace.remove_node(name)
                continue

    def _adjust_to_data(self, trace, data_trace):
        subsampled_idxs = dict()
        for name, site in trace.iter_stochastic_nodes():
            print(site["name"],site["value"])
            # Adjust subsample sites
            if site_is_subsample(site):
                site["fn"] = data_trace.nodes[name]["fn"]
                site["value"] = data_trace.nodes[name]["value"]
            else:
                # Adjust sites under conditionally independent stacks
                orig_cis_stack = site["cond_indep_stack"]
                site["cond_indep_stack"] = data_trace.nodes[name]["cond_indep_stack"]
                assert len(orig_cis_stack) == len(site["cond_indep_stack"])
                site["fn"] = data_trace.nodes[name]["fn"]
                for ocis, cis in zip(orig_cis_stack, site["cond_indep_stack"]):
                    # Select random sub-indices to replay values under conditionally independent stacks.
                    # Otherwise, we assume there is an dependence of indexes between training data
                    # and prediction data.
                    assert ocis.name == cis.name
                    assert not site_is_subsample(site)
                    batch_dim = cis.dim - site["fn"].event_dim
                    subsampled_idxs[cis.name] = subsampled_idxs.get(cis.name,
                                                                    torch.randint(0, ocis.size, (cis.size,),
                                                                                device=site["value"].device))
                    site["value"] = site["value"].index_select(batch_dim, subsampled_idxs[cis.name])
            print(subsampled_idxs)
            print(site["name"],site["value"])


import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, concatenate, Conv2DTranspose, \
    BatchNormalization, Dropout, Lambda
from matplotlib import pyplot as plt

from src.image_loading import sample_image_generator
from src.training_data_generator import sample_generator

def encoder_block(x, filters, kernel_size=3):
    x = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu', kernel_initializer='he_normal',
               padding='same')(x)
    x = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu', kernel_initializer='he_normal',
               padding='same')(x)
    p = MaxPooling2D(2)(x)
    return p, x

def decoder_block(x, skip_connection_features, filters, kernel_size=3):
    x = Conv2DTranspose(filters=filters, kernel_size=3, strides=2, padding='same')(x)
    # Blog about deconvolution vs conv_transpose https: // distill.pub / 2016 / deconv - checkerboard /
    x = concatenate([x, skip_connection_features])
    x = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu', kernel_initializer='he_normal',
               padding='same')(x)
    x = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu', kernel_initializer='he_normal',
               padding='same')(x)
    return x

def unet(img_channels):
    # Shallow U-Net which is enough for detecting angle brackets as they are a relatively small feature
    inputs = Input((None, None, img_channels))
    x = inputs

    p_1, skip_1 = encoder_block(x, 16, 3)
    p_2, skip_2 = encoder_block(p_1, 32, 3)

    # bottleneck
    b = Conv2D(filters=64, kernel_size=3, activation='relu', kernel_initializer='he_normal', padding='same')(p_2)
    b = Conv2D(filters=64, kernel_size=3, activation='relu', kernel_initializer='he_normal', padding='same')(b)

    up_2 = decoder_block(b, skip_2, 32, 3)
    up_1 = decoder_block(up_2, skip_1, 16, 3)

    out = Conv2D(filters=2, kernel_size=1, activation='softmax', padding='same')(up_1)
    model = Model(inputs=inputs, outputs=out)
    return model

if __name__ == '__main__':
    keras.backend.clear_session()

    # Build model
    img_size = (512, 512)
    num_classes = 2
    epochs = 2
    steps_per_epoch = 5  # 15
    # model_name = "model_2_large_augmented_noise_15"
    # model_name = "model_shallow_30"
    # model_name = "model_shallow_flexible_input"
    model_name = "refactor"
    path_trained_model = f"../data/trained_models/{model_name}.h5"
    model = unet(1)
    model.summary()

    train_dataset = tf.data.Dataset.from_generator(sample_generator, output_signature=(
        tf.TensorSpec(shape=(*img_size, 1), dtype=tf.float32), tf.TensorSpec(shape=(*img_size, 2), dtype=tf.float32)))
    train_dataset = train_dataset.batch(4)
    train_dataset = train_dataset.prefetch(buffer_size=tf.data.AUTOTUNE)

    val_dataset = tf.data.Dataset.from_generator(sample_image_generator, output_signature=(
        tf.TensorSpec(shape=(*img_size, 1), dtype=tf.float32), tf.TensorSpec(shape=(*img_size, 2), dtype=tf.float32)))
    val_dataset = val_dataset.batch(4)
    val_dataset = val_dataset.prefetch(buffer_size=tf.data.AUTOTUNE)

    model.compile(optimizer="adam", loss="binary_crossentropy",
                  metrics=[tf.keras.metrics.Accuracy(), tf.keras.metrics.IoU(num_classes=2, target_class_ids=[0])])

    # model = keras.models.load_model(path_trained_model)
    model_history = model.fit(train_dataset, epochs=epochs, steps_per_epoch=steps_per_epoch,
                              validation_data=val_dataset)
    model.save(path_trained_model)

    if True:
        print(model_history.history.keys())
        fig, ax = plt.subplots(3, 1, figsize=(10, 10))
        ax[0].set_title('Loss')
        ax[0].plot(model_history.history['loss'], label='train')
        ax[0].plot(model_history.history['val_loss'], label='validation')
        ax[0].set_xlabel('Epoch')
        ax[0].set_ylabel('Binary Crossentropy Loss')
        ax[0].legend()

        ax[1].set_title('IoU')
        ax[1].plot(model_history.history['io_u'], label='train')
        ax[1].plot(model_history.history['val_io_u'], label='validation')
        ax[1].set_xlabel('Epoch')
        ax[1].set_ylabel('IoU')
        ax[1].legend()

        ax[2].set_title('Accuracy')
        ax[2].plot(model_history.history['accuracy'], label='train')
        ax[2].plot(model_history.history['val_accuracy'], label='validation')
        ax[2].set_xlabel('Epoch')
        ax[2].set_ylabel('Accuracy')
        ax[2].legend()
        plt.savefig(f"../data/plots/{model_name}_loss.png")
        plt.show()

    fig, ax = plt.subplots(3, 3, figsize=(10, 10))
    for img, label in train_dataset.take(2):
        pred = model.predict(img)
        for ix in range(2):
            ax[ix][0].imshow(img[ix, :, :], cmap="gray")
            ax[ix][0].set_title('photo')
            ax[ix][1].imshow(label[ix, :, :, 0], cmap='gray')
            ax[ix][1].set_title('label')
            ax[ix][2].imshow(pred[ix, :, :, 0] >= pred[ix, :, :, 1], cmap='gray')
            # ax[ix][2].imshow(pred[ix, :, :, 0], cmap='gray')
            ax[ix][2].set_title('prediction')

    for img, label in val_dataset.take(2):
        pred = model.predict(img)
        for ix in range(0, 1):
            ax[ix + 2][0].imshow(img[ix, :, :], cmap="gray")
            ax[ix + 2][0].set_title('photo')
            ax[ix + 2][1].imshow(label[ix, :, :, 0], cmap='gray')
            ax[ix + 2][1].set_title('label')
            ax[ix + 2][2].imshow(pred[ix, :, :, 0] >= pred[ix, :, :, 1], cmap='gray')
            # ax[ix + 2][2].imshow(pred[ix, :, :, 0], cmap='gray')
            ax[ix + 2][2].set_title('prediction')
    fig.tight_layout()
    plt.savefig(f"../data/plots/{model_name}_samples.png")
    plt.show()


import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from random import randint, choice
from sklearn.preprocessing import StandardScaler
import random
from sklearn.model_selection import cross_val_score, train_test_split

#Under y overfitting

# 1: ¿Cuales son las principales complicaciones de este planteamiento?
# Escribe tu respuesta a la pregunta 1 en esta celda de código:
'''
El conjunto de entrenamiento es demasiado pequeño y de poca complejidad.
'''

#Generemos 30 datos a partir de la función planteada en la ecuación 1
#Adicionalmente para complicarle un poco el trabajo al modelo agreguemos un poco de ruido
np.random.seed(0)
n_samples = 30

def true_fun(X):
    return np.cos(1.5 * np.pi * X)

X = np.sort(np.random.rand(n_samples))
y_sin_ruido = true_fun(X)
y = true_fun(X) + np.random.randn(n_samples) * 0.1

plt.plot(X, label='X')
plt.plot(y_sin_ruido, label='y (muestra de función original)')
plt.plot(y, label='y (muestra con ruido)')
plt.legend();
plt.show()

#Pregunta 2: ¿Cuántos grados son necesarios?
'''
Tres grados
'''

#A continuación te mostramos una manera de usar pipelines para explorar hiperparámetros.
#Los pipelines son muy útiles al momento de explorar hiperparámetros (en este caso el hiperparámetro que estamos explorando es el máximo grado de libertad en la transformación de la variable X necesario para estimar y)

degrees = [1, 4, 15]

plt.figure(figsize=(14, 5))
for i in range(len(degrees)):
    ax = plt.subplot(1, len(degrees), i + 1)
    plt.setp(ax, xticks=(), yticks=())

    polynomial_features = PolynomialFeatures(degree=degrees[i], include_bias=False)
    linear_regression = LinearRegression()
    pipeline = Pipeline([("polynomial_features", polynomial_features),("linear_regression", linear_regression),])
    pipeline.fit(X[:, np.newaxis], y)

    X_test = np.linspace(0, 1, 100)
    plt.plot(X_test, pipeline.predict(X_test[:, np.newaxis]), label="Modelo creado")
    plt.plot(X_test, true_fun(X_test), label="Función original")
    plt.scatter(X, y, edgecolor="b", s=20, label="Muestra con ruido")
    plt.xlabel("x")
    plt.ylabel("y")
    plt.xlim((0, 1))
    plt.ylim((-2, 2))
    plt.legend(loc="best")
    plt.title("Modelo usando desde X^1 hasta X^{}".format(degrees[i]))
plt.show()

#Pregunta 3:
#¿Como podemos modificar el código anterior para incluir el cálculo del error y
#sistematizar la selección del grado máximo polinomial en la transformación de X?

# Escribe tu respuesta a la pregunta 3 en esta celda de código:
'''
Ajustar los hiperparametros para mejorar la presicion
'''

#Pregunta 4: ¿Cuales son otros hiperparámetros que incrementan la complejidad de los modelos? en el caso de:
# Escribe tu respuesta a la pregunta 4 en esta celda de código:
'''
  Regresión logística:fit_intercept ,penalty
  Arbol de decision:     max_depth,min_samples_split,min_samples_leaf,max_leaf_nodes.
  K-medias: n_clusters, init,max_iter,tol,precomputed_distances, algorithm
  Redes neuronales:Choice,range,list,batch_size,number_of_hidden_layers,QUniform(min_value, max_value, q),
  QLogUniform(min_value, max_value, q),QNormal(mu, sigma, q),QLogNormal(mu, sigma, q),Uniform(min_value, max_value)
  LogUniform(min_value, max_value),Normal(mu, sigma),LogNormal(mu, sigma)
'''

####Reto sobre end-to-end machine learning model####

#2.Importar la informacion en un dataframe
df = pd.read_csv("insurance.csv")
print(df.head())

#3.seleccionar features y crea una regresión lineal,
# luego calcula el score para conocer el performance del modelo

df = df.drop(["region"], axis=1) #excluyendo region

#converting strings to bool
df['sex'] = df['sex'].map({'sex': {'female': True, 'male': False}})
df['smoker'] = df['smoker'].map({'smoker': {'yes': True, 'no': False}})

#llenando nan
df["sex"] = df["sex"].fillna(randint(0, 1))
df["smoker"] = df["smoker"].fillna(randint(0, 1))

reg = LinearRegression()
y = np.array(df['charges']).reshape(-1, 1)
x = np.array(df.drop('charges', axis=1))

reg.fit(x,y)

score_1 = reg.score(x, y)
print('score_1:',score_1)
#score 0.12009819576246927

#4.limpieza, imputación de valores en los vacíos, regresion lineal 2
# Escribe aquí tu código
#filling nan
df.isna().any()
#no hay

#5Crea nuevas variables y transforma las ya existentes si es necesario
#selecciona las variables mas reelevantes con alguna técnica de selección de variables.
#Luego crea nuevamente un modelo de regresión lineal calculando su score.
#Hay muy pocas variables

#6 Escalamiento de variables

y = np.array(df['charges']).reshape(-1, 1)
x = np.array(df.drop('charges', axis=1))

x_std = StandardScaler().fit_transform(x)
y_std = StandardScaler().fit_transform(y)

reg.fit(x_std, y_std)

score_4 = reg.score(x_std, y_std)
print('score_4:',score_4)
#se conserva la score
#score 0.12009819576246927

#7,8 PCA
def get_pca_components(pca, var):
    cumm_var = pca.explained_variance_ratio_
    total_var = 0.
    N_COMPONENTS = 0
    for i in cumm_var:
        N_COMPONENTS += 1
        total_var += i
        if total_var >= var:
            break
    return N_COMPONENTS

pca = PCA().fit(x_std)
n_components = get_pca_components(pca, 0.75)

print(n_components)
# 3 componentes principales (age,bmi,charges)

#7,8 Reduccion de dimencionalidad y regresion
df = df.drop(["sex"], axis=1)
df = df.drop(["smoker"], axis=1)

y = np.array(df['charges']).reshape(-1, 1)
x = np.array(df.drop('charges', axis=1))

x_std = StandardScaler().fit_transform(x)
y_std = StandardScaler().fit_transform(y)

reg.fit(x_std, y_std)

score_5 = reg.score(x_std, y_std)
print('score_5:',score_5)
#se conserva la score
#score 0.12009819576246927

# 7,8 dar complejidad a las variables realizando transformaciones no lineales
#regresion polimial

x_train, x_test, y_train, y_test = train_test_split (x_std, y_std, test_size=0.3)
pol_reg = PolynomialFeatures(degree=3)

x_train_pol = pol_reg.fit_transform(x_train)
x_test_pol = pol_reg.fit_transform(x_test)

#regresion
reg.fit(x_train_pol, y_train)
#prediccion
reg.predict(x_train_pol)

score_6 = reg.score(x_train_pol, y_train)
print('score_6:',score_6)
#Score: 0.15285449105845994

#9: Grafica los 6 scores calculados
ls_scores = [score_1,score_4,score_5,score_6]
plt.plot(ls_scores)
print(plt.show())

#CONCLUSIONES paso 9:
# una parte de la grafica es plana debido a que no hubo cambios en la score
# mas que al usar regresion polinomial


import torch
from argparse import ArgumentParser

class Model(torch.nn.Module):

    @staticmethod
    def add_model_specific_args(parent_parser):
        parser = ArgumentParser(parents=[parent_parser], add_help=False)
        parser.add_argument('--lr', type=float, default=0.001,
                            help='Learning rate.')
        parser.add_argument('--optimizer_name', type=str, default='Adam',
                            help='optimizer: GD, Adam, Adagrad')
        parser.add_argument('--l2', type=float, default=1e-6,
                            help='Weight of l2_regularize in pytorch optimizer.')
        parser.add_argument('--batch_size', type=int, default=128,
                            help='Batch size during training.')
        parser.add_argument('--max_epoch', type=int, default=200,
                            help='Max epochs.')
        parser.add_argument('--es_patience', type=int, default=10,
                            help='#epochs with no improvement after which training will be stopped (early stop).')
        parser.add_argument('--feature_num', type=int, default=64,
                            help='Number of feature for word.')
        parser.add_argument('--dropout_rate', type=float, default=0.5,
                            help='Dropout rate.')
        return parser

    def __init__(self, word_num: int, label_num: int, feature_num: int = 64, dropout_rate: float = 0.5, *args, **kwargs):
        super().__init__()
        self.word_num = word_num
        self.label_num = label_num
        self.feature_num = feature_num
        self.dropout_rate = dropout_rate

    def init_weights(self) -> None:
        for n, p in self.named_parameters():
            if p.requires_grad:
                torch.nn.init.normal_(p, mean = 0, std = 0.01)

    def get_name(self):
        return 'Model'


import logging
import math
import re
import time
import uuid
from decimal import Decimal

import yaml

def load_config(file_path: str) -> dict:
    """
    Load config file and return the content as a dictionary.
    """
    with open(file_path, "r") as file:
        config = yaml.safe_load(file)
    return config

def config_logging(logging_level, log_file: str = None):
    """
    Configures logging to provide a more detailed log format, which includes date time in UTC
    Example: 2021-11-02 19:42:04.849 UTC <logging_level> <log_name>: <log_message>

    Args:
        logging_level (int/str): For logging to include all messages with log levels >= logging_level. Ex: 10 or "DEBUG"
                                 log levels reference - https://docs.python.org/3/library/logging.html#logging-levels
    Keyword Args:
        log_file (str, optional): The filename to pass the logging to a file, instead of using console. Default filemode: "a"
    """
    logging.Formatter.converter = time.gmtime  # date time in GMT/UTC
    logging.basicConfig(
        level=logging_level,
        filename=log_file,
        format="%(asctime)s.%(msecs)03d UTC %(levelname)s %(name)s: %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

def round_up(n, decimals=0):
    """
    Round up a given number n to the specified number of decimal places decimals.
    """
    multiplier = Decimal(10**decimals)
    return Decimal(math.ceil(n * multiplier)) / multiplier

def build_uuid():
    """
    Generate a UUID (Universally Unique Identifier) and return it as a string.
    """
    return str(uuid.uuid4())

def split_line(line):
    """
    Splits a line based on ',(?=")', this means, splits at ',' when it's followed by a '"'.
    Returns the list of splited elements, without the '"' enclosing the elements.
    """
    raw_elements = re.split(r',(?=")', line)
    elements = []
    for e in raw_elements:
        if e[0] == '"' and e[-1] == '"':
            elements.append(e[1:-1])
        else:
            elements.append(e)
    return elements


import os.path
import re
import time
import urllib.request

import pandas as pd
from selenium.webdriver.common.by import By

from Crawling._001_extract_numbers._001_sm3.v1.BrowserModule import Browser, user_path
from Crawling._001_extract_numbers._001_sm3.v1._001_setup_train_6_numbers import CaptchaModelSix

service = Browser(view_mode=True)
d = service.d
all_info_df = pd.read_csv(user_path("gb_url.csv"))
all_account_df = pd.read_csv(user_path("account.csv"))
info_df = all_info_df.iloc[0]
account_df = all_account_df.iloc[0]
url = info_df.access_url

def get_captcha_img():
    d.get(url)

    opener = urllib.request.URLopener()
    opener.addheader('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 '
                                   'Firefox/108.0')
    opener.addheader('Referer', url)
    opener.addheader('Host', info_df.host)
    get = d.get_cookie("PHPSESSID").get("value")
    print(get)
    opener.addheader('Cookie',
                     f'PHPSESSID={get}')
    kaptcha_img = d.find_element(By.CSS_SELECTOR, info_df.img_selector)
    opener.retrieve(kaptcha_img.get_attribute("src"),
                    "delete_me.png")
    return "delete_me.png"

def login():
    captcha_model = CaptchaModelSix()

    d.get(info_df.access_url)

    while True:
        img = get_captcha_img()
        _result = captcha_model.predict(img)
        print(_result)
        if len(re.sub("[^0-9]", "", _result)) == 6:
            break

    print(_result)
    service.wait_selector("#userid").send_keys(account_df.id)
    service.wait_selector("#userpw").send_keys(account_df.pw)
    service.wait_selector(".codes").send_keys(_result)
    service.wait_selector(".btn_login > span:nth-child(1)").click()
    time.sleep(1)

    # d.find_element(By.TAG_NAME, "body").send_keys(Keys.ENTER)
    # time.sleep(1)

    if os.path.exists('delete_me.png'):
        os.system("rm -f delete_me.png")

if __name__ == '__main__':
    login()


"""Grid generators for calendars"""

import math
from dataclasses import dataclass
from datetime import date, timedelta
import calendar
from typing import Optional, List, Set, Dict

HEADERMAPPING = {
    0: "Monday",
    1: "Tuesday",
    2: "Wednesday",
    3: "Thursday",
    4: "Friday",
    5: "Saturday",
    6: "Sunday",
}

@dataclass
class GridItem:
    """A date/letter pair in a grid"""

    date: date
    letter: Optional[str] = None
    label: Optional[str] = None

@dataclass
class CalendarGrid:
    """Everything that is needed to draw a calendar grid"""

    title: str
    headers: List[str]  # Monday, Tuesday, Wednesday
    grid: List[List[Optional[GridItem]]]  # 2d calendar view of grid items

@dataclass
class CalendarGridGenerator:
    """A generator that makes a calendar grid"""

    date_letter_map: Dict[date, Optional[str]]
    label_map: Dict[date, str]

    start_date: date
    end_date: date

    week_start: int = 6

    custom_title: Optional[str] = None

    def get_used_weekdays(self) -> Set[int]:
        """The weekdays that have been used by all the dates together"""

        out = set()

        for letter_date in self.date_letter_map.keys():
            out.add(letter_date.weekday())

        for label_date in self.label_map.keys():
            out.add(label_date.weekday())

        return out

    def get_grid(self) -> CalendarGrid:
        """The 2d grid calendar view of days and times"""

        used_weekdays = self.get_used_weekdays()

        # All grid items and headers will be referenced off the calendar object, for start of week consistency
        cal = calendar.Calendar(firstweekday=self.week_start)

        # The weekdays as they should be in columns across our calendar
        ordered_weekdays = [
            weekday for weekday in cal.iterweekdays() if weekday in used_weekdays
        ]

        # Monday, Tuesday, Wednesday - just those weekdays that were used in this calendar
        used_headers = [HEADERMAPPING[weekday] for weekday in ordered_weekdays]

        out = CalendarGrid(headers=used_headers, grid=[], title=self.title)

        # Walk backwards until we get our first weekday
        internal_start_date = self.start_date

        # This is a fix to the Jan 2022 issue - the first week was all None because
        # Jan 1st was on a Saturday, which then got walked back to December 27, which
        # then all got excluded because none of the dates were in range

        # Walk forwards until we hit a day of the week we care about
        while internal_start_date.weekday() not in ordered_weekdays:
            internal_start_date += timedelta(days=1)

        # Walk backwards until we hit the start of the week
        target_weekday = ordered_weekdays[0]
        while internal_start_date.weekday() != target_weekday:
            internal_start_date -= timedelta(days=1)

        # The plus one is for the Jan 2022 edge condition, and the extra row gets
        # excluded at the end if it was added
        total_weeks = math.ceil((self.end_date - internal_start_date).days / 7) + 1
        full_grid = []
        for week_index in range(total_weeks):
            week_start = internal_start_date + timedelta(days=week_index * 7)
            row: List[Optional[date]] = []
            for day_index in range(len(used_weekdays)):
                cell_date = week_start + timedelta(days=day_index)

                if cell_date >= self.start_date and cell_date <= self.end_date:
                    row.append(cell_date)
                else:
                    row.append(None)

            if row != [None] * len(row):
                # Make sure we don't dump complete nothing rows in - account
                # for the Jan 2022 special case
                full_grid.append(row)

        # Now we have a date corresponding to the actual first day that might be on our calendar,
        # even though we might not display that date because it's outside of our range

        # Create the grid we're going to be using

        def get_entry(date_val: Optional[date]) -> Optional[GridItem]:
            """
            Get the entry for this item in the calendar grid
            Will either be a grid item, or a None if it's out of our month range
            """

            if not date_val:
                return None

            letter = self.date_letter_map.get(date_val)
            label = self.label_map.get(date_val)

            return GridItem(date_val, letter, label)

        for week in full_grid:
            week_entries = [get_entry(d) for d in week]
            if week_entries != [None] * len(used_weekdays):
                # The calendar grid can generate a phantom unused row over an unused day - don't keep it
                out.grid.append(week_entries)

        return out

    @property
    def title(self) -> str:
        """Get the title of a calendar with this grid"""

        if self.custom_title:
            return self.custom_title

        if (
            self.start_date.year == self.end_date.year
            and self.start_date.month == self.end_date.month
        ):
            sample_date = date(self.start_date.year, self.end_date.month, 1)

            return sample_date.strftime("%B %Y")

        start_date_str = self.start_date.strftime("%b %d, %Y")
        end_date_str = self.end_date.strftime("%b %d, %Y")

        return f"{start_date_str} to {end_date_str}"


import sys
from PyQt5 import QtWidgets
from client_ui import Ui_Form
import ct
import os
import re
import clock
import threading
import  time

class MyPyQT_Form(QtWidgets.QWidget, Ui_Form):
    def __init__(self):
        super(MyPyQT_Form, self).__init__()
        self.setupUi(self)
        self.btn_start_test.clicked.connect(self.on_start_test)
        self.btn_start_error.clicked.connect(self.on_see_error)
        self.clientthread = ct.ClientThread()
        self.flag = 0
        self.error_detail = 0
        self.score = 0
        self.list = None
        self.current_page = None
        self.TimeOver = 0

    def on_start_test(self):
        #切到试题

        if self.flag == 0:
            self.flag += 1
            self.btn_last_topic.setVisible(True)
            self.btn_next_topic.setVisible(True)
            self.Topicend.setVisible(False)
            self.Topicend2.setVisible(False)
            self.Topicend3.setVisible(False)
            self.Topicend4.setVisible(False)
            self.btn_start_test.setText("提交答卷")
            clientthread = self.clientthread
            self.list = clientthread.get_topic()
            self.current_page = 0
            self.classify_topic()
            self.stackedWidget.setCurrentIndex(self.current_page)
            self.startTimer()
            clock.startTimer()  #计时器
            self.TimeOver = 0

        elif self.flag == 1:  # 提交答案
            if self.TimeOver == 0:
                clock.root.destroy()
                self.flag += 1
                msg = self.get_sum()
                clientthread = self.clientthread
                clientthread.send_msg(str(msg))
                self.Topicend.setVisible(True)
                self.Topicend2.setVisible(True)
                self.Topicend3.setVisible(True)
                self.Topicend4.setVisible(True)
                self.btn_next_topic.setVisible(False)
                self.btn_last_topic.setVisible(False)
                self.current_page = 11
                self.stackedWidget.setCurrentIndex(self.current_page)
                self.btn_start_test.setText("查看答题情况")

                clientthread.send_msg(str(self.score))
                clientthread.send_msg(str(self.score))
            else:
                self.tktime.setText("已经超过作答时间无法交卷")
                pass

        elif self.flag == 2 and (self.error_detail < len(self.error_list)):
            self.btn_start_error.setVisible(True)
            self.btn_start_test.setText("查看下一题")
            self.Topicend.setText("你答对了 " + str(self.right_num) + " 题,"
                                  + " 你的得分为 " + str(self.score))
            self.error_num = len(self.error_list)
            self.error_detail += 1
            self.current_page += 1
            self.flag += 1
            self.stackedWidget.setCurrentIndex(self.current_page)
            self.retroction_situtation(self.error_detail)
        elif self.flag == 3 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)

            self.flag += 1
        elif self.flag == 4 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)
            self.flag += 1
        elif self.flag == 5 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)
            self.flag += 1
        elif self.flag == 6 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)
            self.flag += 1
        elif self.flag == 7 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)

            self.flag += 1
        elif self.flag == 8 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)
            self.flag += 1
        elif self.flag == 9 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)
            self.flag += 1
        elif self.flag == 10 and (self.error_detail < self.error_num - 1):
            self.error_detail += 1
            self.retroction_situtation(self.error_detail)
            self.flag += 1
        elif self.flag == 11 and (self.error_detail < self.error_num - 1):
            self.retroction_situtation(self.error_detail)
        else:
            self.retroction_situtation(self.error_detail)

    def on_last_topic(self):
        if (self.current_page <= 0):
            pass
        else:
            self.current_page -= 1
            self.stackedWidget.setCurrentIndex(self.current_page)

    def on_next_topic(self):
        if (self.current_page >= 9):
            pass
        else:
            self.current_page += 1
            self.stackedWidget.setCurrentIndex(self.current_page)

    def classify_topic(self):
        pattern_A = re.compile('A.?(.*?)B')  # 去掉题目前面标号的正则
        pattern_B = re.compile('B.?(.*?)C')
        pattern_C = re.compile('C.?(.*?)D')
        pattern_D = re.compile('D.?(.*?)$')

        topic_list = self.list

        # 0
        self.Topic0.setText("1、 " + topic_list[0].information)
        self.radioButton_0A.setText("A、 " + pattern_A.findall(topic_list[0].option)[0])
        self.radioButton_0B.setText("B、 " + pattern_B.findall(topic_list[0].option)[0])
        self.radioButton_0C.setText("C、 " + pattern_C.findall(topic_list[0].option)[0])
        self.radioButton_0D.setText("D、 " + pattern_D.findall(topic_list[0].option)[0])

        # 1
        self.Topic1.setText("2、 " + topic_list[1].information)
        self.radioButton_1A.setText("A、 " + pattern_A.findall(topic_list[1].option)[0])
        self.radioButton_1B.setText("B、 " + pattern_B.findall(topic_list[1].option)[0])
        self.radioButton_1C.setText("C、 " + pattern_C.findall(topic_list[1].option)[0])
        self.radioButton_1D.setText("D、 " + pattern_D.findall(topic_list[1].option)[0])

        # 2
        self.Topic2.setText("3、 " + topic_list[2].information)
        self.radioButton_2A.setText("A、 " + pattern_A.findall(topic_list[2].option)[0])
        self.radioButton_2B.setText("B、 " + pattern_B.findall(topic_list[2].option)[0])
        self.radioButton_2C.setText("C、 " + pattern_C.findall(topic_list[2].option)[0])
        self.radioButton_2D.setText("D、 " + pattern_D.findall(topic_list[2].option)[0])

        # 3
        self.Topic3.setText("4、 " + topic_list[3].information)
        self.radioButton_3A.setText("A、 " + pattern_A.findall(topic_list[3].option)[0])
        self.radioButton_3B.setText("B、 " + pattern_B.findall(topic_list[3].option)[0])
        self.radioButton_3C.setText("C、 " + pattern_C.findall(topic_list[3].option)[0])
        self.radioButton_3D.setText("D、 " + pattern_D.findall(topic_list[3].option)[0])

        # 4
        self.Topic4.setText("5、 " + topic_list[4].information)
        self.radioButton_4A.setText("A、 " + pattern_A.findall(topic_list[4].option)[0])
        self.radioButton_4B.setText("B、 " + pattern_B.findall(topic_list[4].option)[0])
        self.radioButton_4C.setText("C、 " + pattern_C.findall(topic_list[4].option)[0])
        self.radioButton_4D.setText("D、 " + pattern_D.findall(topic_list[4].option)[0])

        # 5
        self.Topic5.setText("6、 " + topic_list[5].information)
        self.radioButton_5A.setText("A、 " + pattern_A.findall(topic_list[5].option)[0])
        self.radioButton_5B.setText("B、 " + pattern_B.findall(topic_list[5].option)[0])
        self.radioButton_5C.setText("C、 " + pattern_C.findall(topic_list[5].option)[0])
        self.radioButton_5D.setText("D、 " + pattern_D.findall(topic_list[5].option)[0])

        # 6
        self.Topic6.setText("7、 " + topic_list[6].information)
        self.radioButton_6A.setText("A、 " + pattern_A.findall(topic_list[6].option)[0])
        self.radioButton_6B.setText("B、 " + pattern_B.findall(topic_list[6].option)[0])
        self.radioButton_6C.setText("C、 " + pattern_C.findall(topic_list[6].option)[0])
        self.radioButton_6D.setText("D、 " + pattern_D.findall(topic_list[6].option)[0])

        # 7
        self.Topic7.setText("8、 " + topic_list[7].information)
        self.radioButton_7A.setText("A、 " + pattern_A.findall(topic_list[7].option)[0])
        self.radioButton_7B.setText("B、 " + pattern_B.findall(topic_list[7].option)[0])
        self.radioButton_7C.setText("C、 " + pattern_C.findall(topic_list[7].option)[0])
        self.radioButton_7D.setText("D、 " + pattern_D.findall(topic_list[7].option)[0])

        # 8
        self.Topic8.setText("9、 " + topic_list[8].information)
        self.radioButton_8A.setText("A、 " + pattern_A.findall(topic_list[8].option)[0])
        self.radioButton_8B.setText("B、 " + pattern_B.findall(topic_list[8].option)[0])
        self.radioButton_8C.setText("C、 " + pattern_C.findall(topic_list[8].option)[0])
        self.radioButton_8D.setText("D、 " + pattern_D.findall(topic_list[8].option)[0])

        # 9
        self.Topic9.setText("10、 " + topic_list[9].information)
        self.radioButton_9A.setText("A、 " + pattern_A.findall(topic_list[9].option)[0])
        self.radioButton_9B.setText("B、 " + pattern_B.findall(topic_list[9].option)[0])
        self.radioButton_9C.setText("C、 " + pattern_C.findall(topic_list[9].option)[0])
        self.radioButton_9D.setText("D、 " + pattern_D.findall(topic_list[9].option)[0])

    def get_sum(self):
        answer_list = self.get_checked()
        self.score = 0
        self.right_num = 0
        self.error_list_answer = []  # 错题集自选答案
        self.error_list = []  # 错的序号

        for i in range(0, 10):
            if answer_list[i] == self.list[i].answer:
                self.score += 10
                self.right_num += 1
            else:
                self.error_list.append(i)
                self.error_list_answer.append(answer_list[i])

        print("总分： " + str(self.score))
        print("答对题数: " + str(self.right_num))
        return self.score

    def on_see_error(self):
        if self.error_detail == 0:
            self.retroction_situtation(self.error_detail)
        else:
            self.error_detail -= 1
            self.flag -= 1
            self.retroction_situtation(self.error_detail)

    def get_checked(self):
        # 0
        check = []
        if self.radioButton_0A.isChecked():
            check0 = 'A'
        elif self.radioButton_0B.isChecked():
            check0 = 'B'
        elif self.radioButton_0C.isChecked():
            check0 = 'C'
        elif self.radioButton_0D.isChecked():
            check0 = 'D'
        else:
            check0 = 'N'
        # 1
        if self.radioButton_1A.isChecked():
            check1 = 'A'
        elif self.radioButton_1B.isChecked():
            check1 = 'B'
        elif self.radioButton_1C.isChecked():
            check1 = 'C'
        elif self.radioButton_1D.isChecked():
            check1 = 'D'
        else:
            check1 = 'N'
        # 2
        if self.radioButton_2A.isChecked():
            check2 = 'A'
        elif self.radioButton_2B.isChecked():
            check2 = 'B'
        elif self.radioButton_2C.isChecked():
            check2 = 'C'
        elif self.radioButton_2D.isChecked():
            check2 = 'D'
        else:
            check2 = 'N'
        # 3
        if self.radioButton_3A.isChecked():
            check3 = 'A'
        elif self.radioButton_3B.isChecked():
            check3 = 'B'
        elif self.radioButton_3C.isChecked():
            check3 = 'C'
        elif self.radioButton_3D.isChecked():
            check3 = 'D'
        else:
            check3 = 'N'
        # 4
        if self.radioButton_4A.isChecked():
            check4 = 'A'
        elif self.radioButton_4B.isChecked():
            check4 = 'B'
        elif self.radioButton_4C.isChecked():
            check4 = 'C'
        elif self.radioButton_4D.isChecked():
            check4 = 'D'
        else:
            check4 = 'N'
        # 5
        if self.radioButton_5A.isChecked():
            check5 = 'A'
        elif self.radioButton_5B.isChecked():
            check5 = 'B'
        elif self.radioButton_5C.isChecked():
            check5 = 'C'
        elif self.radioButton_5D.isChecked():
            check5 = 'D'
        else:
            check5 = 'N'
        # 6
        if self.radioButton_6A.isChecked():
            check6 = 'A'
        elif self.radioButton_6B.isChecked():
            check6 = 'B'
        elif self.radioButton_6C.isChecked():
            check6 = 'C'
        elif self.radioButton_6D.isChecked():
            check6 = 'D'
        else:
            check6 = 'N'
        # 7
        if self.radioButton_7A.isChecked():
            check7 = 'A'
        elif self.radioButton_7B.isChecked():
            check7 = 'B'
        elif self.radioButton_7C.isChecked():
            check7 = 'C'
        elif self.radioButton_7D.isChecked():
            check7 = 'D'
        else:
            check7 = 'N'
        # 8
        if self.radioButton_8A.isChecked():
            check8 = 'A'
        elif self.radioButton_8B.isChecked():
            check8 = 'B'
        elif self.radioButton_8C.isChecked():
            check8 = 'C'
        elif self.radioButton_8D.isChecked():
            check8 = 'D'
        else:
            check8 = 'N'
        # 9
        if self.radioButton_9A.isChecked():
            check9 = 'A'
        elif self.radioButton_9B.isChecked():
            check9 = 'B'
        elif self.radioButton_9B.isChecked():
            check9 = 'C'
        elif self.radioButton_9D.isChecked():
            check9 = 'D'
        else:
            check9 = 'N'
        check.append(check0)
        check.append(check1)
        check.append(check2)
        check.append(check3)
        check.append(check4)
        check.append(check5)
        check.append(check6)
        check.append(check7)
        check.append(check8)
        check.append(check9)
        return check

    # 上端调用 生成具体错题情况
    def retroction_situtation(self, num):
        self.Topicend2.setText("error" + str(num) + ":  " + self.list[self.error_list[num]].information)
        self.Topicend3.setText(self.list[self.error_list[num]].option)
        self.Topicend4.setText(
            "the right option is  " + self.list[self.error_list[num]].answer +
            "  you choose " + self.error_list_answer[num]
        )
        pass
    # 写哪些题错了

    def stop(self):
        for i in range(300, -1, -1):
            minute = int(i / 60)
            sec = i % 60
            self.tktime.setText('剩余作答时间 : ' + str(minute) + '分' + str(sec) + '秒')
            time.sleep(1)
        self.TimeOver = 1
        print(self.TimeOver)
        print("计时结束")

    # 创建并启动线程
    def startTimer(self):
        t = threading.Thread(target=self.stop)
        t.start()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    my_pyqt_form = MyPyQT_Form()
    my_pyqt_form.show()
    sys.exit(app.exec_())


import inspect
import re
import types

import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.utils.validation import check_is_fitted

from . import funcs

__all__ = ['strip', 'AVAILABLE_MODELS']

class Func:

    def __init__(self, name, code):
        self.name = name
        self.code = code
        code = compile(code, "<string>", "exec")
        self.func = types.FunctionType(code.co_consts[0], globals(), name)

    def __call__(self, x):
        return self.func(x)

    def __repr__(self):
        return self.code.replace('\n\n\n', '\n\n')

class FuncPipeline:

    def __init__(self, funcs):
        self.funcs = funcs

    def __call__(self, x):
        for func in self.funcs:
            x = func(x)
        return x

    def __repr__(self):
        code = '\n\n'.join(repr(func) for func in self.funcs)

        code += '\n\n'
        code += 'def pipeline(x):\n'
        for func in self.funcs:
            code += f'    x = {func.name}(x)\n'
        code += '    return x'

        return code

mapping = {
    'sklearn': {
        'LinearRegression': funcs.sklearn.linear_model.linear_regression,
        'LogisticRegression': funcs.sklearn.linear_model.logistic_regression,
        'Normalizer': funcs.sklearn.preprocessing.normalizer,
        'StandardScaler': funcs.sklearn.preprocessing.standard_scaler,
        'TfidfVectorizer': funcs.sklearn.feature_extraction.text.tfidf_vectorizer
    }
}

AVAILABLE = '\n'.join(
    mod + '\n' + '\n'.join(f'    {m}' for m in sorted(mapping[mod]))
    for mod in mapping
)

def handle_input_names(x):
    return [x[name] for name in names]

def make_handle_input_names(names: str) -> Func:
    code = inspect.getsource(handle_input_names)
    loc = code.splitlines()
    return Func('handle_input_names', loc[0] + f'\n    names = {names}\n' + loc[1])

def handle_output_names(x):
    return dict(zip(names, x))

def make_handle_output_names(names: str) -> Func:
    code = inspect.getsource(handle_output_names)
    loc = code.splitlines()
    return Func('handle_output_names', loc[0] + f'\n    names = {names}\n' + loc[1])

def _strip(model):

    # Check if the model is supported
    mod = model.__class__.__module__.split('.')[0]
    try:
        func = mapping[mod][model.__class__.__name__]
    except KeyError:
        raise KeyError(f"I don't know how to unstrip {model.__class__.__name__} from {mod}.")

    # The model needs to have called fit
    check_is_fitted(model)

    # Now we just have to edit the function's source code by inserting the parameters
    code = inspect.getsource(func)
    code = re.sub(r'\(.+\)', '(x)', code, count=1)

    params_code = ''

    for param_name in inspect.signature(func).parameters:
        if param_name == 'x':
            continue

        param_val = getattr(model, param_name)
        if isinstance(param_val, np.ndarray):
            param_val = param_val.tolist()
        if isinstance(param_val, str):
            param_val = f"'{param_val}'"

        params_code += f'    {param_name} = {param_val}\n'

    # Insert the parameter specification code
    loc = code.splitlines()
    code = loc[0] + '\n\n' + params_code + '\n' + '\n'.join(loc[1:])

    return Func(func.__name__, code)

def strip(model, input_names=None, output_names=None):

    if isinstance(model, Pipeline):
        func = FuncPipeline([_strip(step) for _, step in model.steps])
    else:
        func = _strip(model)

    # If input names are specified, then we'll assume that the input x is a dictionary and not a
    # list. We'll be able to handle by first mapping the dictionary values to a list in the
    # specified order.
    if input_names is not None:
        handle_input_names = make_handle_input_names(input_names)
        if isinstance(func, FuncPipeline):
            func.funcs.insert(0, handle_input_names)
        else:
            func = FuncPipeline([handle_input_names, func])

    # If output names are specified, then we'll produce a dictionary instead of a list.
    if output_names is not None:
        handle_output_names = make_handle_output_names(output_names)
        if isinstance(func, FuncPipeline):
            func.funcs.append(handle_output_names)
        else:
            func = FuncPipeline([func, handle_output_names])

    return func


import time
from PIL import Image
import numpy as np
import datetime

IS_RASPI = False
try:
    import spidev
    import RPi.GPIO as GPIO
    IS_RASPI = True
except:
   pass

print("SPI LOAD:", IS_RASPI)
print("##### PYTHON NIL #####")

DISP = 27
SCS = 23 #22
VCOMSEL = 17
BACKLIGHT = 4 #if GPIO 4(pin7) is already used with other devics, set 18(pin12) etc

#0x90 4bit update mode
#0x80 3bit update mode (fast)
#0x88 1bit update mode (most fast, but 2-color)
UPDATE_MODE = 0x80

SCREEN_WIDTH = 400
SCREEN_HEIGHT = 240

class MipDisplay():

    spi = None

    buff_width = int(SCREEN_WIDTH*3/8)+2 #for 3bit update mode
    #buff_width = int(SCREEN_WIDTH*4/8)+2 #for 4bit update mode

    def __init__(self):

        if not IS_RASPI:
            return

        self.spi = spidev.SpiDev()
        self.spi.open(0, 0)
        self.spi.mode = 0b00 #SPI MODE0
        #self.spi.max_speed_hz = 2000000 #MAX 2MHz
        self.spi.max_speed_hz =  9500000 #overclocking
        self.spi.no_cs
        time.sleep(0.1)     #Wait

        GPIO.setmode(GPIO.BCM)
        GPIO.setup(DISP, GPIO.OUT)
        GPIO.setup(SCS, GPIO.OUT)
        GPIO.setup(VCOMSEL, GPIO.OUT)

        GPIO.output(SCS, 0)     #1st=L
        GPIO.output(DISP, 1)    #1st=Display On
        #GPIO.output(DISP, 0)   #1st=No Display
        #GPIO.output(VCOMSEL, 0) #L=VCOM(1Hz)
        GPIO.output(VCOMSEL, 1) #L=VCOM(1Hz)
        time.sleep(0.1)

        GPIO.setup(BACKLIGHT, GPIO.OUT)
        #self.backlight = GPIO.PWM(BACKLIGHT, 60)
        self.backlight = GPIO.PWM(BACKLIGHT, 64)
        self.backlight.start(0)

        self.pre_img = np.zeros((SCREEN_HEIGHT, self.buff_width), dtype='uint8')
        self.img_buff_rgb8 = np.empty((SCREEN_HEIGHT, self.buff_width), dtype='uint8')
        self.img_buff_rgb8[:,0] = UPDATE_MODE
        self.img_buff_rgb8[:,1] = np.arange(SCREEN_HEIGHT)
        self.img_buff_rgb8[:,0] = self.img_buff_rgb8[:,0] + (np.arange(SCREEN_HEIGHT) >> 8)

        self.clear()

    def clear(self):
        if not IS_RASPI:
            return
        GPIO.output(SCS, 1)
        time.sleep(0.000006)
        self.spi.xfer2([0b00100000,0]) # ALL CLEAR MODE
        GPIO.output(SCS, 0)
        time.sleep(0.000006)

    def no_update(self):
        if not IS_RASPI:
            return
        GPIO.output(SCS, 1)
        time.sleep(0.000006)
        self.spi.xfer2([0b00000000,0]) # NO UPDATE MODE
        GPIO.output(SCS, 0)
        time.sleep(0.000006)

    def blink(self, sec):
        if not IS_RASPI:
            return
        s = sec
        state = True
        interval = 0.5
        while s > 0:
            GPIO.output(SCS, 1)
            time.sleep(0.000006)
            if state:
                self.spi.xfer2([0b00010000,0]) # BLINK(BLACK) MODE
            else:
                self.spi.xfer2([0b00011000,0]) # BLINK(WHITE) MODE
            GPIO.output(SCS, 0)
            time.sleep(interval)
            s -= interval
            state = not state
        self.no_update()

    def inversion(self, sec):
        if not IS_RASPI:
            return
        s = sec
        state = True
        interval = 0.5
        while s > 0:
            GPIO.output(SCS, 1)
            time.sleep(0.000006)
            if state:
                self.spi.xfer2([0b00010100,0]) # INVERSION MODE
            else:
                self.no_update()
            GPIO.output(SCS, 0)
            time.sleep(interval)
            s -= interval
            state = not state
        self.no_update()

    #def pil_to_screen(self, pil_img):
    def pil_to_screen(self, img_file):

        im_array = np.array(Image.open(img_file))
        #im_array = np.array(pil_img)

        t = datetime.datetime.now()

        #3bit mode update
        self.img_buff_rgb8[:,2:] = np.packbits(
            ((im_array > 128).astype('uint8')).reshape(SCREEN_HEIGHT,SCREEN_WIDTH*3),
            axis=1
            )
        img_bytes = bytearray()

        #differential update
        rewrite_flag = False
        diff_lines = np.where(np.sum((self.img_buff_rgb8 == self.pre_img), axis=1) != self.buff_width)[0]
        print("diff ", int(len(diff_lines)/SCREEN_HEIGHT*100), "%")
        img_bytes = self.img_buff_rgb8[diff_lines].tobytes()
        if len(diff_lines) > 0:
            rewrite_flag = True
        self.pre_img[diff_lines] = self.img_buff_rgb8[diff_lines]

        print("Loading images... :", (datetime.datetime.now()-t).total_seconds(),"sec")

        t = datetime.datetime.now()
        if IS_RASPI:
            GPIO.output(SCS, 1)
            time.sleep(0.000006)
            if len(img_bytes) > 0:
                self.spi.xfer3(img_bytes)
            #dummy output for ghost line
            self.spi.xfer2([0x00000000,0])
            time.sleep(0.000006)
            GPIO.output(SCS, 0)
        print("Drawing images... :", (datetime.datetime.now()-t).total_seconds(),"sec")

    def set_brightness(self, brightness):

        b = brightness
        if brightness >= 100:
            b = 100
        elif brightness <= 0:
            b = 0

        if not IS_RASPI:
            return
        self.backlight.ChangeDutyCycle(b)
        time.sleep(0.05)

    def backlight_blink(self):
        if not IS_RASPI:
            return
        for x in range(2):
            for pw in range(0,100,1):
                self.backlight.ChangeDutyCycle(pw)
                time.sleep(0.05)
            for pw in range(100,0,-1):
                self.backlight.ChangeDutyCycle(pw)
                time.sleep(0.05)

    def quit(self):
        if not IS_RASPI:
            return
        #self.clear()
        GPIO.output(DISP, 1)
        time.sleep(0.1)
        self.set_brightness(0)
        self.spi.close()
        GPIO.cleanup()

if __name__ == '__main__':
    m = MipDisplay()
    m.set_brightness(50)
    #Get image from https://os.mbed.com/teams/JapanDisplayInc/code/MIP8f_FRDM_sample/. Need "LPM027M128x (400x240)".
    m.pil_to_screen('img/004_blood3.bmp')
    time.sleep(1)
    m.pil_to_screen('img/004_blood3.bmp')
    time.sleep(1)
    m.pil_to_screen('img/005_blood4.bmp')
    time.sleep(1)
    m.pil_to_screen('img/n00011 400x240 navi.bmp')
    time.sleep(1)
    m.pil_to_screen('img/n00012 400x240 navi.bmp')
    time.sleep(1)
    m.pil_to_screen('img/003_b1.bmp')
    time.sleep(1)
    m.blink(3)
    m.inversion(3)
    m.quit()


"""
    brief:
        - post-processes and plots the results of the training using PPO, either for MF-DRL or MB-DRL
        - plots the results of the controlled case using the best policy in comparison to the uncontrolled case

    dependencies:
        - 'ppo_data_loader.py' for handling the loading, sorting and merging of all training data
        - 'analyze_frequency_spectrum.py' for plotting the frequency spectrum of cl- and cd of PPO-training and the
           final results

    prerequisites:
        - execution of the 'run_training.py' function in the 'test_training' directory in order to conduct a training
          and generate trajectories within the CFD environment (https://github.com/OFDataCommittee/drlfoam)
        - this training can either be model-free or model-based

    optional:
        - execution of simulation for the best policy from training, also results of a simulation without control
        - in this case, the results of the training are not averaged over multiple seed values, since the final policy
          corresponds to a specific training of one seed value
"""
import re
from os.path import join

import pandas as pd
import matplotlib.pyplot as plt

from typing import Union
from os import mkdir, path
from matplotlib.patches import Circle, Rectangle

from ppo_data_loader import *
from analyze_frequency_spectrum import analyze_frequencies_final_result, analyze_frequencies_ppo_training,\
    analyze_frequencies_probes_final_result

def plot_coefficients_vs_episode(settings: dict, cd_mean: Union[list, pt.Tensor], cd_std: Union[list, pt.Tensor],
                                 cl_mean: Union[list, pt.Tensor], cl_std: Union[list, pt.Tensor],
                                 actions_mean: Union[list, pt.Tensor] = None,
                                 actions_std: Union[list, pt.Tensor] = None,
                                 n_cases: int = 1, plot_action: bool = False,
                                 ylabel: list = ["$\\bar{c}_L$", "$\\bar{c}_D$", "$\\bar{\omega}$"]) -> None:
    """
    plot cl, cd and actions (if specified) depending on the episode (training)

    :param settings: dict containing all the paths etc.
    :param cd_mean: mean cd received over the training periode
    :param cd_std: corresponding standard deviation of cd throughout the training periode
    :param cl_mean: mean cl received over the training periode
    :param cl_std: corresponding standard deviation of cl throughout the training periode
    :param actions_mean: mean actions (omega) done over the training periode
    :param actions_std: corresponding standard deviation of the actions done over the training periode
    :param n_cases: number of cases to compare (= number of imported data)
    :param plot_action: if 'True' cl, cd and actions will be plotted, otherwise only cl and cd will be plotted
    :param ylabel: ylabels for plots [cl, cd, action]
    :return: None
    """
    if plot_action:
        fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(6, 3))
        n_subfig = 3
    else:
        fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))
        n_subfig = 2

    for c in range(n_cases):
        for i in range(n_subfig):
            if i == 0:
                ax[i].plot(range(len(cl_mean[c])), cl_mean[c], color=settings["color"][c], label=settings["legend"][c])
                ax[i].fill_between(range(len(cl_mean[c])), cl_mean[c] - cl_std[c], cl_mean[c] + cl_std[c],
                                   color=settings["color"][c], alpha=0.3)
                ax[i].set_ylabel(ylabel[0])

            elif i == 1:
                ax[i].plot(range(len(cd_mean[c])), cd_mean[c], color=settings["color"][c])
                ax[i].fill_between(range(len(cd_mean[c])), cd_mean[c] - cd_std[c], cd_mean[c] + cd_std[c],
                                   color=settings["color"][c], alpha=0.3)
                ax[i].set_ylabel(ylabel[1])

            elif plot_action:
                ax[i].plot(range(len(actions_mean[c])), actions_mean[c], color=settings["color"][c])
                ax[i].fill_between(range(len(actions_mean[c])), actions_mean[c] - actions_std[c],
                                   actions_mean[c] + actions_std[c], color=settings["color"][c], alpha=0.3)
                ax[i].set_ylabel(ylabel[2])

            ax[i].set_xlabel("$e$")

    fig.tight_layout()
    fig.legend(loc="upper center", framealpha=1.0, ncol=3)
    fig.subplots_adjust(wspace=0.35, top=0.88)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", "coefficients_vs_episode.png"),
                dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_rewards_vs_episode(settings: dict, reward_mean: Union[list, pt.Tensor], reward_std: Union[list, pt.Tensor],
                            n_cases: int = 0) -> None:
    """
    plots the mean rewards received throughout the training periode and the corresponding standard deviation

    :param settings: dict containing all the paths etc.
    :param reward_mean: mean rewards received over the training periode
    :param reward_std: corresponding standard deviation of the rewards received over the training periode
    :param n_cases: number of cases to compare (= number of imported data)
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(12, 6))
    for c in range(n_cases):
        ax.plot(range(len(reward_mean[c])), reward_mean[c], color=settings["color"][c], label=settings["legend"][c])
        ax.fill_between(range(len(reward_mean[c])), reward_mean[c] - reward_std[c], reward_mean[c] + reward_std[c],
                        color=settings["color"][c], alpha=0.3)

    ax.set_ylabel("$\\bar{r}$")
    ax.set_xlabel("$e$")
    ax.set_xlim(0, max([len(i) for i in reward_mean]))
    fig.tight_layout()
    ax.legend(loc="lower right", framealpha=1.0, ncol=2)
    fig.subplots_adjust(wspace=0.2)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", "rewards_vs_episode.png"),
                dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_cl_cd_alpha_beta(settings: dict, controlled_cases: Union[list, pt.Tensor],
                          uncontrolled_case: Union[list, pt.Tensor] = None, plot_coeffs=True, factor: int = 10) -> None:
    """
    plot either cl and cd vs. time or alpha and beta vs. time

    :param settings: dict containing all the paths etc.
    :param controlled_cases: results from the loaded cases with active flow control
    :param uncontrolled_case: reference case containing results from uncontrolled flow past cylinder
    :param plot_coeffs: 'True' means cl and cd will be plotted, otherwise alpha and beta will be plotted wrt to time
    :param factor: factor for converting the physical time into non-dimensional time, t^* = u * t / d
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(14, 6))

    if plot_coeffs:
        keys = ["t", "cl", "cd"]
        save_name = "comparison_cl_cd"
        ax[1].set_ylim(2.95, 3.25)  # Re = 100
        # ax[1].set_ylim(2, 3.75)          # Re = 500
        n_cases = range(len(settings["case_name"]) + 1)
        ylabels = ["$c_L$", "$c_D$"]
        x_min = 0
    else:
        keys = ["t", "alpha", "beta"]
        save_name = "comparison_alpha_beta"
        ylabels = ["$\\alpha$", "$\\beta$"]
        n_cases = range(1, len(settings["case_name"]) + 1)
        x_min = 4 * factor      # control starts at t = 4s, so there are no alpha & beta available for t < 4s

    for c in n_cases:
        for i in range(2):
            if i == 0:
                if c == 0:
                    ax[i].plot(uncontrolled_case[keys[0]] * factor, uncontrolled_case[keys[1]], color="black",
                               label="uncontrolled")
                else:
                    ax[i].plot(controlled_cases[c - 1][keys[0]] * factor, controlled_cases[c - 1][keys[1]],
                               color=settings["color"][c - 1], label=settings["legend"][c - 1])
                ax[i].set_ylabel(ylabels[0], fontsize=13)
            else:
                if c == 0:
                    ax[i].plot(uncontrolled_case[keys[0]] * factor, uncontrolled_case[keys[2]], color="black")
                else:
                    ax[i].plot(controlled_cases[c - 1][keys[0]] * factor, controlled_cases[c - 1][keys[2]],
                               color=settings["color"][c - 1])
                ax[i].set_ylabel(ylabels[1], fontsize=13)

            ax[1].set_xlabel("$t^*$", fontsize=14)
            ax[i].set_xlim(x_min, controlled_cases[0]["t"].iloc[-1] * factor)
    fig.tight_layout()
    fig.legend(loc="upper center", framealpha=1.0, fontsize=10, ncol=2)
    fig.subplots_adjust(wspace=0.2, top=0.84)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", f"{save_name}.png"), dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_omega(settings: dict, controlled_cases: Union[list, pt.Tensor], factor: int = 10) -> None:
    """
    plot omega (actions) vs. time

    :param settings: dict containing all the paths etc.
    :param controlled_cases: results from the loaded cases with active flow control
    :param factor: factor for converting the physical time into non-dimensional time, t^* = u * t / d
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 5))
    for c in range(len(settings["case_name"])):
        ax.plot(controlled_cases[c]["t"] * factor, controlled_cases[c]["omega"], color=settings["color"][c],
                label=settings["legend"][c])

    ax.set_ylabel("$\omega$", fontsize=13)
    ax.set_xlabel("$t^*$", fontsize=13)
    fig.tight_layout()
    fig.subplots_adjust(top=0.91)
    plt.legend(loc="upper right", framealpha=1.0, ncol=1)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", "omega_controlled_case.png"),
                dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_variance_of_beta_dist(settings: dict, var_beta_dist: Union[list, pt.Tensor], n_cases: int = 0) -> None:
    """
    plots the mean rewards received throughout the training periode and the corresponding standard deviation

    :param settings: dict containing all the paths etc.
    :param var_beta_dist: computed variance of the beta-function wrt episode
    :param n_cases: number of cases to compare (= number of imported data)
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(12, 6))
    for c in range(n_cases):
        ax.plot(range(len(var_beta_dist[c])), var_beta_dist[c], color=settings["color"][c], label=settings["legend"][c])

    ax.set_ylabel("$mean$ $variance$ $of$ $beta-distribution$", fontsize=13)
    ax.set_xlabel("$e$", fontsize=13)
    ax.legend(loc="upper right", framealpha=1.0, fontsize=10, ncol=2)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", "var_beta_distribution.png"),
                dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_cl_cd_trajectories(settings: dict, data: list, number: int, e: int = 1, factor: int = 10) -> None:
    """
    plots the trajectory of cl and cd for different episodes of the training, meant to use for either comparing MF-
    trajectories to trajectories generated by the environment models or comparing trajectories from environment models
    run with different settings to each other

    :param settings: setup containing all the paths etc.
    :param data: trajectory data to plot
    :param number: number of the trajectory within the data set (either within the episode or in total)
    :param e: episode number
    :param factor: factor for converting the physical time into non-dimensional time, t^* = u * t / d
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15, 5))
    epochs = pt.tensor(list(range(len(data[0]["cl"][1, :, number])))) / factor
    for n in range(len(data)):
        for i in range(3):
            try:
                if i == 0:
                    # 2nd episode is always MB (if MB-DRL was used)
                    ax[i].plot(epochs, data[n]["cl"][e, :, number], color=settings["color"][n],
                               label=f"{settings['legend'][n]}, episode {e + 1}")
                    ax[i].set_ylabel("$c_L$", fontsize=13)
                elif i == 1:
                    ax[i].plot(epochs, data[n]["cd"][e, :, number], color=settings["color"][n])
                    ax[i].set_ylabel("$c_D$", fontsize=13)
                else:
                    ax[i].plot(epochs, data[n]["actions"][e, :, number], color=settings["color"][n])
                    ax[i].set_ylabel("$omega$", fontsize=13)
                ax[i].set_xlabel("$t^*$", fontsize=13)
            except IndexError:
                print("omit plotting trajectories of failed cases")
    fig.tight_layout()
    fig.legend(loc="upper right", framealpha=1.0, fontsize=10, ncol=2)
    fig.subplots_adjust(wspace=0.25, top=0.90)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots",
                     f"comparison_traj_cl_cd_{e}.png"), dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_total_reward(settings: dict, reward_mean: list, reward_std: list, n_cases: int) -> None:
    """
    plot the total rewards of the complete training for each case

    :param settings: dict containing all the paths etc.
    :param reward_mean: mean total rewards received in the training
    :param reward_std: corresponding standard deviation of the total rewards received in the training
    :param n_cases: number of cases to compare (= number of imported data)
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 5))
    for c in range(n_cases):
        ax.errorbar(c + 1, reward_mean[c], yerr=reward_std[c], color=settings["color"][c], fmt="o", capsize=5,
                    label=settings["legend"][c])

    ax.set_ylabel("$total$ $reward$", usetex=True, fontsize=13)
    ax.set_xlabel("$case$ $number$", usetex=True, fontsize=13)
    ax.set_xticks(range(1, n_cases + 1, 1))
    ax.legend(loc="upper right", framealpha=1.0, fontsize=10, ncol=1)
    plt.grid(which="major", axis="y", linestyle="--", alpha=0.85, color="black", lw=1)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", "total_rewards.png"), dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_numerical_setup(settings: dict) -> None:
    """
    plot the domain of the cylinder case

    :param settings: setup containing the import and save path
    :return: None
    """
    pattern = r"\d.\d+ \d.\d+ \d.\d+"
    path = "".join([settings["main_load_path"], settings["path_controlled"], settings["case_name"][0],
                    settings["path_final_results"]])
    with open("".join([path, settings["path_to_probes"]]), "r") as f:
        loc = f.readlines()

    # get coordinates of probes, omit appending empty lists and map strings to floats
    coord = [re.findall(pattern, line) for line in loc if re.findall(pattern, line)]
    pos_probes = pt.tensor([list(map(float, i[0].split())) for i in coord])

    # get coordinates of domain and cylinder
    with open("".join([path, "system/blockMeshDict"]), "r") as f:
        loc = f.readlines()

    # structure in blockMeshDict always the same: [lengthX 2.2, lengthY 0.41, cylinderX 0.2, cylinderY 0.2, radius 0.05]
    l, h, pos_x, pos_y, r = [float(loc[i].strip(";\n").split()[1]) for i in range(16, 21)]

    # plot cylinder, probe locations and annotate the domain
    fig, ax = plt.subplots(figsize=(10, 3))
    ax.plot(pos_probes[:, 0], pos_probes[:, 1], linestyle="None", marker="o", color="red", label="probes")
    # dummy point for legend
    ax.scatter(-10, -10, marker="o", color="black", alpha=0.4, label="cylinder")
    circle = Circle((pos_x, pos_y), radius=r, color="black", alpha=0.4)
    rectangle = Rectangle((0, 0), width=l, height=h, edgecolor="black", linewidth=2, facecolor="none")
    ax.add_patch(circle)
    ax.add_patch(rectangle)
    fig.legend(loc="lower right", framealpha=1.0, fontsize=10, ncol=2)
    ax.set_xlim(0, l)
    ax.set_ylim(0, h)
    ax.set_xticks([])
    ax.set_yticks([])

    # annotate inlet & outlet
    plt.arrow(-0.05, -0.05, 0.1, 0.0, color="black", head_width=0.02, clip_on=False)
    plt.arrow(-0.05, -0.05, 0.0, 0.1, color="black", head_width=0.02, clip_on=False)
    plt.arrow(-0.1, h * 2 / 3 + 0.025, 0.075, -0.05, color="black", head_width=0.015, clip_on=False)
    plt.arrow(-0.1 + l, h * 2 / 3, 0.075, -0.05, color="black", head_width=0.015, clip_on=False)

    plt.annotate("$inlet$", (-0.17, h * 2 / 3 + 0.05), annotation_clip=False, usetex=True, fontsize=13)
    plt.annotate("$\\frac{x}{d}$", (0.1, -0.065), annotation_clip=False, usetex=True, fontsize=16)
    plt.annotate("$\\frac{y}{d}$", (-0.1, 0.065), annotation_clip=False, usetex=True, fontsize=16)
    plt.annotate("$outlet$", (-0.2 + l, h * 2 / 3 + 0.01), annotation_clip=False, usetex=True, fontsize=13)

    # annotate the dimensions & position of the domain
    pos = {"xy": [(0, h + 0.04), (0, h), (l, h), (pos_x - r - 0.01, pos_y - 0.1), (l, h), (l, 0), (l + 0.04, h),
                  (pos_x, pos_y + 0.9 * r), (0, pos_y)],
           "xytxt": [(l, h + 0.04), (0, h + 0.075), (l, h + 0.075), (pos_x + r + 0.01, pos_y - 0.1), (l + 0.075, h),
                     (l + 0.075, 0),
                     (l + 0.04, 0), (pos_x, h), (pos_x - 0.9 * r, pos_y)],
           "style": [("<->", "-"), ("-", "--"), ("-", "--"), ("<->", "-"), ("-", "--"), ("-", "--"), ("<->", "-"),
                     ("<->", "-"), ("<->", "-")]
           }
    for i in range(len(pos["style"])):
        plt.annotate("", xy=pos["xy"][i], xytext=pos["xytxt"][i],
                     arrowprops=dict(arrowstyle=pos["style"][i][0], color="black", linestyle=pos["style"][i][1]),
                     annotation_clip=False)

    plt.annotate(f"${l / (2 * r)}$", (l / 2, h + 0.07), annotation_clip=False, usetex=True, fontsize=12)
    plt.annotate(f"${h / (2 * r)}$", (l + 0.07, h / 2), annotation_clip=False, usetex=True, fontsize=12)
    plt.annotate("$d$", (pos_x - r / 4, pos_y - 3 * r), usetex=True, fontsize=12)
    plt.annotate("${:.2f}$".format((h - (pos_y + r)) / (2 * r)), (pos_x + 0.025, pos_y + 2.25 * r), usetex=True,
                 fontsize=12)
    plt.annotate("${:.2f}$".format((pos_x - r) / (2 * r)), (pos_x - 3.25 * r, pos_y + 0.5 * r), usetex=True,
                 fontsize=12)

    ax.plot((pos_x - r, pos_x - r), (pos_y, pos_y - 0.15), color="black", linestyle="--", lw=1)
    ax.plot((pos_x + r, pos_x + r), (pos_y, pos_y - 0.15), color="black", linestyle="--", lw=1)
    ax.plot(pos_x, pos_y, marker="+", color="black")

    ax.set_aspect("equal")
    fig.tight_layout()
    fig.subplots_adjust(left=0.1, right=0.92)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots", "domain_setup.png"), dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_train_validation_loss(settings: dict, mse_train: Union[list, pt.Tensor], mse_val: Union[list, pt.Tensor],
                               mse_train_cd: Union[list, pt.Tensor], mse_val_cd: Union[list, pt.Tensor],
                               std_dev_train: Union[list, pt.Tensor], std_dev_val: Union[list, pt.Tensor],
                               std_dev_train_cd: Union[list, pt.Tensor], std_dev_val_cd: Union[list, pt.Tensor],
                               case: int = 1) -> None:
    """
    plots the avg. train- and validation loss and the corresponding std. deviation of the environment models wrt to
    epochs

    :param settings: path where the plot should be saved
    :param mse_train: tensor containing the (mean) training loss of the cl-p env. model
    :param mse_val: tensor containing the (mean) validation loss of the cl-p env. model
    :param mse_train_cd: tensor containing the (mean) training loss of the cd env. model
    :param mse_val_cd: tensor containing the (mean) validation loss of the cd env. model
    :param std_dev_train: tensor containing the (std. deviation) training loss of the cl-p env. model
    :param std_dev_val: tensor containing the (std. deviation) validation loss of the cl-p env. model
    :param std_dev_train_cd: tensor containing the (std. deviation) training loss of the cd env. model
    :param std_dev_val_cd: tensor containing the (std. deviation) validation loss of the cd env. model
    :param case: name to append for savin img
    :return: None
    """
    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(14, 6))
    for i in range(2):
        if i == 0:
            x = range(len(mse_train))
            ax[i].plot(x, mse_train, color="blue")
            ax[i].plot(x, mse_val, color="red")
            ax[i].fill_between(x, mse_val - std_dev_val, mse_val + std_dev_val, color="red", alpha=0.3)
            ax[i].fill_between(x, mse_train - std_dev_train, mse_train + std_dev_train, color="blue", alpha=0.3)
            ax[i].set_ylabel("$MSE$ $loss$", usetex=True, fontsize=13)
            ax[i].set_xlabel("$epoch$ $number$", usetex=True, fontsize=13)
            ax[i].set_title("$environment$ $model$ $for$ $c_L$ $\&$ $p_i$", usetex=True, fontsize=14)
            ax[i].set_yscale("log")

        else:
            x = range(len(mse_train_cd))
            ax[i].plot(x, mse_train_cd, color="blue", label="training loss")
            ax[i].plot(x, mse_val_cd, color="red", label="validation loss")
            ax[i].fill_between(x, mse_train_cd - std_dev_train_cd, mse_train_cd + std_dev_train_cd, color="blue",
                               alpha=0.3)
            ax[i].set_xlabel("$epoch$ $number$", usetex=True, fontsize=13)
            ax[i].set_title("$environment$ $model$ $for$ $c_D$", usetex=True, fontsize=14)
            ax[i].set_yscale("log")
            ax[i].set_ylabel("$MSE$ $loss$", usetex=True, fontsize=13)

    ax[1].legend(loc="upper right", framealpha=1.0, fontsize=10, ncol=2)
    fig.tight_layout()
    fig.subplots_adjust(wspace=0.2)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots",
                     "train_val_losses_case{case}.png"),
                dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

def plot_mean_std_trajectories(settings: dict, data: list, factor: int = 10) -> None:
    """
    plots the trajectory of cl and cd for different episodes of the training, meant to use for either comparing MF-
    trajectories to trajectories generated by the environment models or comparing trajectories from environment models
    run with different settings to each other

    :param settings: setup containing all the paths etc.
    :param data: trajectory data to plot
    :param factor: factor for converting the physical time into non-dimensional time, t^* = u * t / d
    :return: None
    """
    fig, ax = plt.subplots(nrows=4, ncols=2, figsize=(6, 8), sharey="col", sharex="all")
    epochs = pt.tensor(list(range(len(data[0]["cl"][1, :, 0])))) / factor
    e = [24, 74, 124, 199]
    for n in range(len(data)):
        for k in range(4):          # k = rows
            for i in range(2):      # i = cols
                if i == 0:
                    mean_tmp = pt.mean(data[n]["cd"][e[k], :, :], dim=1)
                    std_tmp = pt.std(data[n]["cd"][e[k], :, :], dim=1)
                    if k == 0:
                        # 2nd episode is always MB (if MB-DRL was used)
                        ax[k][i].plot(epochs, mean_tmp, color=settings["color"][n], label=settings['legend'][n])
                        ax[k][i].fill_between(epochs, mean_tmp - std_tmp, mean_tmp + std_tmp, color=settings["color"][n],
                                              alpha=0.3)
                    else:
                        ax[k][i].plot(epochs, mean_tmp, color=settings["color"][n])
                        ax[k][i].fill_between(epochs, mean_tmp - std_tmp, mean_tmp + std_tmp, color=settings["color"][n],
                                              alpha=0.3)

                    ax[k][i].set_ylabel("$\\bar{c}_D$")
                else:
                    mean_tmp = pt.mean(data[n]["cl"][e[k], :, :], dim=1)
                    std_tmp = pt.std(data[n]["cl"][e[k], :, :], dim=1)
                    ax[k][i].plot(epochs, mean_tmp, color=settings["color"][n])
                    ax[k][i].fill_between(epochs, mean_tmp - std_tmp, mean_tmp + std_tmp, color=settings["color"][n],
                                          alpha=0.3)
                    if i == 1:
                        ax[k][i].set_ylabel("$\\bar{c}_L$")

                ax[k][i].set_xlim(0, data[0]["cl"].size()[1] / factor)
    ax[-1][0].set_xlabel("$t^*$")
    ax[-1][1].set_xlabel("$t^*$")
    fig.tight_layout()
    fig.legend(loc="upper center", framealpha=1.0, ncol=2)
    fig.subplots_adjust(wspace=0.3, top=0.9)
    plt.savefig(join(settings["main_load_path"], settings["path_controlled"], "plots",
                     "comparison_traj_cd_mean_std.png"), dpi=340)
    plt.show(block=False)
    plt.pause(2)
    plt.close("all")

if __name__ == "__main__":
    # Setup
    setup = {
        "main_load_path": r"/home/janis/Hiwi_ISM/results_drlfoam_MB/",
        "path_to_probes": r"postProcessing/probes/0/p",  # path to the file containing trajectories of probes
        "path_uncontrolled": r"run/uncontrolled_re100/",  # path to uncontrolled reference case
        "path_controlled": r"run/final_routine_AWS/",
        "path_final_results": r"results_best_policy/",  # path to the results using the best policy
        "case_name": ["e200_r10_b10_f8_MF/", "e200_r10_b10_f8_MB_1model/",
                      "e200_r10_b10_f8_MB_5models/", "e200_r10_b10_f8_MB_5models_threshold40/",
                      "e200_r10_b10_f8_MB_10models_threshold50/", "e200_r10_b10_f8_MB_10models_threshold30/"],
        # "case_name": ["e200_r10_b10_f8_MF/seed4/", "e200_r10_b10_f8_MB_1model/seed2/",
        #               "e200_r10_b10_f8_MB_5models/seed1/", "e200_r10_b10_f8_MB_5models_threshold40/seed1/",
        #               "e200_r10_b10_f8_MB_10models_threshold50/seed0/",
        #               "e200_r10_b10_f8_MB_10models_threshold30/seed4/"],
        "e_trajectory": [4, 9, 24, 49, 74, 99, 124, 149, 174, 199],   # episodes trajectories (cl & cd, not avg.)
        "n_probes": 12,  # number of probes placed in flow field
        "avg_over_cases": True,  # if cases should be averaged over, e.g. different seeds
        "plot_final_res": False,  # if the final policy already ran in openfoam, plot the results
        "param_study": False,  # flag if parameter study, only used for generating legend entries automatically
        "mark_e_cfd": False,  # flag if CFD episodes should be marked (in case of avg., 1st seed is taken)
        "color": ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
                  '#bcbd22', '#17becf'],  # default color cycle
        "legend": ["MF", "MB, $N_{m} = 1$", "MB, $N_{m} = 5, N_{thr} = 3$", "MB, $N_{m} = 5, N_{thr} = 2$",
                   "MB, $N_{m} = 10, N_{thr} = 5$", "MB, $N_{m} = 10, N_{thr} = 3$"]
    }

    # create directory for plots
    if not path.exists(join(setup["main_load_path"], setup["path_controlled"], "plots")):
        mkdir(join(setup["main_load_path"], setup["path_controlled"], "plots"))

    # use latex fonts
    plt.rcParams.update({"text.usetex": True})

    # load all the data
    all_data = load_all_data(setup)

    # average the trajectories episode-wise
    averaged_data = average_results_for_each_case(all_data)

    # for parameter study: generate legend entries automatically
    if setup["param_study"]:
        if setup["avg_over_cases"]:
            setup["legend"] = [f"b = {averaged_data['buffer_size'][c]}, l = {averaged_data['len_traj'][c]} s" for c in
                               range(len(averaged_data["len_traj"]))]
        else:
            setup["legend"] = [f"seed = {c}" for c in range(len(averaged_data["len_traj"]))]

    # print info amount CFD episodes, assuming 1st case is MF
    for i in range(len(averaged_data["MF_episodes"])):
        print(f"{averaged_data['MF_episodes'][i]} CFD episodes for case {i}")

    # plots for avg. the trajectories for cl & cd and plot then vs. t
    plot_mean_std_trajectories(setup, all_data)

    # plot variance of the beta-distribution wrt episodes
    plot_variance_of_beta_dist(setup, averaged_data["var_beta_fct"], n_cases=len(setup["case_name"]))

    # plot mean rewards wrt to episode
    plot_rewards_vs_episode(setup, reward_mean=averaged_data["mean_rewards"], reward_std=averaged_data["std_rewards"],
                            n_cases=len(setup["case_name"]))

    # plot mean cl and cd wrt to episode
    plot_coefficients_vs_episode(setup, cd_mean=averaged_data["mean_cd"], cd_std=averaged_data["std_cd"],
                                 cl_mean=averaged_data["mean_cl"], cl_std=averaged_data["std_cl"],
                                 n_cases=len(setup["case_name"]), plot_action=False)

    # plot total rewards received in the training
    plot_total_reward(setup, averaged_data["tot_mean_rewards"], averaged_data["tot_std_rewards"],
                      n_cases=len(setup["case_name"]))

    # compare trajectories over the course of the training
    for e in setup["e_trajectory"]:
        plot_cl_cd_trajectories(setup, all_data, number=1, e=e)

    # do frequency analysis of the cd- and cl-trajectories wrt episode number for each case
    for case in range(len(setup["case_name"])):
        analyze_frequencies_ppo_training(setup, all_data[case], case=case + 1)

        # also plot training- and validation losses of the environment models, if MB-DRL was used
        if "train_loss_cd" in all_data[case]:
            keys = 2 * ["train_loss_cl_p", "val_loss_cl_p", "train_loss_cd", "val_loss_cd"]
            key = [f"mean_" + k if idx < 4 else f"std_" + k for idx, k in enumerate(keys)]
            plot_train_validation_loss(setup, averaged_data["losses"][case][key[0]],
                                       averaged_data["losses"][case][key[1]], averaged_data["losses"][case][key[2]],
                                       averaged_data["losses"][case][key[3]], averaged_data["losses"][case][key[4]],
                                       averaged_data["losses"][case][key[5]], averaged_data["losses"][case][key[6]],
                                       averaged_data["losses"][case][key[7]], case + 1)

    # if the cases are run in openfoam using the trained network (using the best policy), plot the results
    if setup["plot_final_res"]:
        # plot the numerical setup for one case, assuming it's the same for all cases
        plot_numerical_setup(setup)

        # import the trajectory of the uncontrolled case
        uncontrolled = pd.read_csv(join(setup["main_load_path"], setup["path_uncontrolled"], "postProcessing", "forces",
                                        "0", "coefficient.dat"), skiprows=13, header=0,
                                   sep=r"\s+", usecols=[0, 1, 2], names=["t", "cd", "cl"])
        p_uncontrolled = pd.read_csv("".join([setup["main_load_path"], setup["path_uncontrolled"],
                                              setup["path_to_probes"]]), skiprows=setup["n_probes"] + 1, header=0,
                                     names=["t"] + [f"probe_{i}" for i in range(setup["n_probes"])], sep=r"\s+")

        controlled, p_controlled, traj = [], [], []
        for case in range(len(setup["case_name"])):
            # import the trajectories of the controlled cases
            controlled.append(pd.read_csv(join(setup["main_load_path"], setup["path_controlled"],
                                               setup["case_name"][case], setup["path_final_results"], "postProcessing",
                                               "forces", "0", "coefficient.dat"), skiprows=13, header=0,
                                          sep=r"\s+", usecols=[0, 1, 2], names=["t", "cd", "cl"]))

            traj.append(pd.read_csv(join(setup["main_load_path"], setup["path_controlled"], setup["case_name"][case],
                                         setup["path_final_results"], "trajectory.csv"), header=0, sep=r",",
                                    usecols=[0, 1, 2, 3], names=["t", "omega", "alpha", "beta"]))

            p_controlled.append(pd.read_csv(join(setup["main_load_path"], setup["path_controlled"],
                                                 setup["case_name"][case], setup["path_final_results"],
                                                 setup["path_to_probes"]), skiprows=setup["n_probes"] + 1,
                                            header=0, names=["t"] + [f"probe_{i}" for i in range(setup["n_probes"])],
                                            sep=r"\s+"))

        # plot cl and cd of the controlled cases vs. the uncontrolled cylinder flow
        plot_cl_cd_alpha_beta(setup, controlled, uncontrolled, plot_coeffs=True)

        # plot omega of the controlled cases
        plot_omega(setup, traj)

        # plot alpha and beta of the controlled cases
        plot_cl_cd_alpha_beta(setup, traj, plot_coeffs=False)

        # analyze frequency spectrum of cl- and cd-trajectories, therefore insert empty list int traj. data, so the idx
        # matches with the other data (since uncontrolled case hass no alpha, beta, omega)
        traj.insert(0, [])
        analyze_frequencies_final_result(setup, uncontrolled, controlled, traj)

        # analyze frequency spectrum of probes
        analyze_frequencies_probes_final_result(setup, p_uncontrolled, p_controlled, n_probes=setup["n_probes"])


from pycocotools.coco import COCO
import numpy as np

import cv2
import mediapipe as mp
import os, time
import com_detection as comm
import com_files as comf

#pip install pycocotools

COCO_PATH = "/home/leo/coco"
COCO_TYPE='train2017'
#COCO_TYPE='val2017'
annFile='{}/annotations/instances_{}.json'.format(COCO_PATH, COCO_TYPE)

LABELS_PREFIX = "labels_coco"

IGNORE_FACE_OVERLAP = True

# initialize COCO api for instance annotations
coco=COCO(annFile)

def coco_show_categories(coco):
    # display COCO categories and supercategories
    cats = coco.loadCats(coco.getCatIds())
    nms=[cat['name'] for cat in cats]
    print('COCO categories: \n{}\n'.format(' '.join(nms)))
    nms = set([cat['supercategory'] for cat in cats])
    print('COCO supercategories: \n{}'.format(' '.join(nms)))

def coco_get_image_ids_with_cat(cats = ['person']):
    # get all images containing given categories, select one at random
    #catIds = coco.getCatIds(catNms=['person','dog','skateboard'])
    #catIds = coco.getCatIds(catNms=['person'])
    catIds = coco.getCatIds(catNms=cats)
    imgIds = coco.getImgIds(catIds=catIds)
    print('len:', len(imgIds))
    return catIds, imgIds

def calc_iou(box1, box2):
    """
    :param box1: = [xmin1, ymin1, xmax1, ymax1]
    :param box2: = [xmin2, ymin2, xmax2, ymax2]
    :return:
    """
    xmin1, ymin1, xmax1, ymax1 = box1
    xmin2, ymin2, xmax2, ymax2 = box2
    #
    s1 = (xmax1 - xmin1) * (ymax1 - ymin1)  # b1 area
    s2 = (xmax2 - xmin2) * (ymax2 - ymin2)  # b2 area

    #
    xmin = max(xmin1, xmin2)
    ymin = max(ymin1, ymin2)
    xmax = min(xmax1, xmax2)
    ymax = min(ymax1, ymax2)

    w = max(0, xmax - xmin)
    h = max(0, ymax - ymin)
    a1 = w * h  # C∩G
    a2 = s1 + s2 - a1
    iou = a1 / a2 #iou = a1/ (s1 + s2 - a1)
    return iou

def coco_get_face_info_from_yoloface_fast_predictions(imagef, width, height):
    #show coco yoloface_fast_predictions

    #"/home/leo/coco/images/val2017/000000036936.jpg"
    #"/home/leo/coco/yoloface_fast_predictions/val2017/xxxx.csv"
    cocoface_label = imagef.replace("images", "yoloface_fast_predictions") + ".csv"
    facesline = comm.read_filelist(cocoface_label)

    facebox = []
    for face in facesline:
        items = face.split(",")
        if (len(items) != 6):
            continue
        #print(items[1:])
        #'/data/coco/val2017/000000036936.jpg,0.9891075,104.2295,414.4043,148.12671,440.77728'
        ppath, prob, y0, x0, y1, x1 = items
        x0 = float(x0) / width
        y0 = float(y0) / height
        x1 = float(x1) / width
        y1 = float(y1) / height
        prob = float(prob)
        w = x1 - x0
        h = y1 - y0
        #if w < comm.YOLO_FACE_MIN_SIZE or h < comm.YOLO_FACE_MIN_SIZE: continue
        if w < comm.YOLO_FACE_MIN_SIZE or h < comm.YOLO_FACE_MIN_SIZE:
            continue
        facebox.append([prob,x0,y0,x1,y1])

    overlap = False

    if len(facebox) > 1:
        idx_used = set()
        for ii in range(0, len(facebox)-1):
            box0 = facebox[ii][1:]
            for jj in range(ii+1, len(facebox)):
                box1 = facebox[jj][1:]
                iou = calc_iou(box0, box1)
                if iou > 0:
                    overlap = True
                    #print(ii, jj, box0, box1, 'overlap', 'iou:', iou)

    infos = []
    for face in facebox:
        prob,x0,y0,x1,y1 = face
        w = x1-x0
        h = y1-y0
        a_box = [x0, y0, w, h]
        info = [comm.YOLO_FACE_ID, comm.id_to_names(comm.YOLO_FACE_ID), prob, a_box]
        #print(info)
        infos.append(info)

    return overlap, infos

def coco_cv_show(catIds, imgIds):
    txtfile_cc = (0, 255, 120)
    coco_images = []
    for img_id in imgIds:
        img = coco.loadImgs(img_id)[0]

        imagef = f"{COCO_PATH}/images/{COCO_TYPE}/{img['file_name']}"
        #print('imagef', imagef)             # /home/leo/coco/images/val2017/000000413689.jpg
        #print('img_id', img_id)             # 413689
        #print('coco_url', img['coco_url'])  # http://images.cocodataset.org/val2017/000000108503.jpg

        frame = cv2.imread(imagef)
        height_ori, width_ori, _  = frame.shape

        frame = cv2.resize(frame, comm.YOLO_IMAGE_SIZE)
        height, width, _  = frame.shape

        # load and display instance annotations
        annIds = coco.getAnnIds(imgIds=img['id'], catIds=catIds, iscrowd=None)
        anns = coco.loadAnns(annIds)

        object_labels = []

        for idx, ann in enumerate(anns):
            #print(idx)
            #print(' iscrowd', ann['iscrowd'])
            #print(ann)
            if ann['iscrowd']:
                continue

            coco_id = ann['category_id']
            coco_name = comm.COCO_ID_NAME_MAP[coco_id]
            a_box = ann['bbox']
            a_box = [a_box[0]/width_ori, a_box[1]/height_ori, a_box[2]/width_ori, a_box[3]/height_ori]

            if a_box[2] < comm.YOLO_OBJECT_MIN_SIZE or a_box[3] < comm.YOLO_OBJECT_MIN_SIZE:
                continue

            info  = [coco_id, coco_name, 1.0, a_box]
            comm.draw_info_on_image(frame, width, height, info, txtfile_cc, 1)

            #coco id to imvt_yolo_id
            if coco_name in comm.IMVT_CLS_NAMES:
                yolo_id = comm.IMVT_CLS_NAMES.index(coco_name)
                info  = [yolo_id, coco_name, 1.0, a_box]
                object_labels.append(comm.info_to_yolo_string(info))

        person_num = len(object_labels)

        face_overlap, face_infos = coco_get_face_info_from_yoloface_fast_predictions(imagef, width_ori, height_ori)

        if IGNORE_FACE_OVERLAP and face_overlap:
            continue    #SKIP OVERLAP

        if len(face_infos) > 0:
            for info in face_infos:
                object_labels.append(comm.info_to_yolo_string(info))

        if len(object_labels) > 0:
            labelf = imagef.replace("images", LABELS_PREFIX).replace(".jpg", ".txt")
            comf.ensure_file_dir(labelf)
            comf.write_list(labelf, object_labels)
            print('labelf', labelf, 'faces:', len(face_infos), 'person:', person_num)
            coco_images.append(imagef)

        #cv2.imshow('Coco', frame)
        #if (cv2.waitKey(1000*3) & 0xFF == ord(comm.EXIT_KEY)): break

    coco_image_list = os.path.join(COCO_PATH, f"coco_{COCO_TYPE}_filelists.txt")
    comf.write_list_to_file(coco_images, coco_image_list)

coco_show_categories(coco)

catIds, imgIds = coco_get_image_ids_with_cat(cats = ['person'])

#coco_show_image(catIds, imgIds)
coco_cv_show(catIds, imgIds)


# -*- coding: utf-8 -*-
import json
import re
import os

DEFULT_WORD_FILES = [r"./he-data/nouns.txt", r"./he-data/verbs.txt"]

def create_dict(files):
    word_dict = {}
    key_words = set()
    CHARS_PATTERN = re.compile(r"""[^אבגדהוזחטיכלמנסעפצקרשתןףץםך"]""")
    for file_name in files:
        with open(file_name, 'r', encoding="utf-8") as file:
            file_text = file.read()
            word_sections = [l for l in file_text.split("\n-") if l != []]
            for word_types in word_sections:
                word_types_arr = word_types.split("\n")
                word_types_arr = [CHARS_PATTERN.sub('', word) for word in word_types_arr if CHARS_PATTERN.sub('', word) != ""]
                if len(word_types_arr):
                    base_word = word_types_arr[0]
                    key_words.add(base_word)
                    for word in word_types_arr:
                        if word not in key_words and word not in word_dict or word == base_word:
                            word_dict[word] = base_word

    return word_dict

def write_dict_to_file(dict_name, files):
    with open(dict_name, "w") as outfile:
        word_dict = create_dict(files)
        json.dump(word_dict, outfile)

class HebrewProcessor:
    def __init__(self, files = DEFULT_WORD_FILES):
        self.word_dict = create_dict(files)

    def process_word(self, word):
        PREFIXES = ["ו", "ה", "ב", "ש", "כ", "ל", "מ", "מה", "מש", "וה", "וש", "ומ", "ול", "וב"]

        if word in self.word_dict:
            return self.word_dict[word]

        for pref in PREFIXES:
            no_pref = word[word.startswith(pref) and len(pref):]
            if no_pref in self.word_dict:
                return self.word_dict[no_pref]

        return word


import json
import os
import tempfile

import datasets

# dd = datasets.load_dataset(path="/home/roberta/loko/projects/sentence_transformer/apps",data_files="example_data.json")
# print(dd.data)

example = {
  "idx": [
    32326,
    27449,
    60108,
    23141,
    35226,
    66852,
    65093,
    47847,
    39440,
    56428,
    6798,
    6824,
    56503,
    51288,
    39024,
    58847
  ],
  "sentence": [
    "klein , charming in comedies like american pie and dead-on in election , ",
    "be fruitful ",
    "soulful and ",
    "the proud warrior that still lingers in the souls of these characters ",
    "covered earlier and much better ",
    "wise and powerful ",
    "a powerful and reasonably fulfilling gestalt ",
    "smart and newfangled ",
    "it too is a bomb . ",
    "guilty about it ",
    "while the importance of being earnest offers opportunities for occasional smiles and chuckles ",
    "stevens ' vibrant creative instincts ",
    "great artistic significance ",
    "what does n't this film have that an impressionable kid could n't stand to hear ? ",
    "working from a surprisingly sensitive script co-written by gianni romoli ... ",
    "eight crazy nights is a total misfire . "
  ],
  "label": [
    1,
    1,
    1,
    1,
    0,
    1,
    1,
    1,
    0,
    0,
    1,
    1,
    1,
    1,
    1,
    0
  ]
}
# a = datasets.load_dataset_builder((example))
# print(a)
tfile = tempfile.NamedTemporaryFile(mode="w+", suffix=".json")
json.dump(example, tfile)
tfile.flush()
print(tfile.name.split("/")[-1])
nome = tfile.name.split("/")[-1]
path = "/".join(tfile.name.split("/")[:-1])
dd = datasets.load_dataset(path=path,data_files=nome)

print("ddddd",dd.data)

tfile.close()
print("==========")
dd2 = datasets.load_dataset(path=path,data_files=nome)

print("ddddd",dd2.data)


from django.contrib import messages
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.shortcuts import render, redirect
from .forms import UnitCreateForm
from .models import Unit

# Create your views here.
def create(request):
    form = UnitCreateForm(request.POST or None)
    context = {}
    if request.method == "POST":
        if form.is_valid():
            form.save()
            return redirect("units:list")

    context['form'] = form
    return render(request, 'unit/create.html', context)

def update(request, pk):
    context = {}
    try:
        data = Unit.objects.get(id=pk)
    except Unit.DoesNotExist:
        messages.error(request, "Object not found.")
        return redirect("units:list")

    form = UnitCreateForm(instance=data)
    if request.method == "POST":
        form = UnitCreateForm(request.POST, instance=data)
        if form.is_valid():
            form.save()
            return redirect("units:list")

    context['form'] = form
    return render(request, 'unit/create.html', context)

def list(request):
    context = {}
    data = Unit.objects.all()

    per_page = 2
    paginator = Paginator(data, per_page)
    page = request.GET.get('page')
    try:
        data = paginator.page(page)
    except PageNotAnInteger:
        data = paginator.page(1)
    except EmptyPage:
        data = paginator.page(paginator.num_pages)
    context['data'] = data
    return render(request, 'unit/list.html', context)

# def detail(request, pk):
#     context = {}
#     try:
#         data = Unit.objects.get(id=pk)
#     except Unit.DoesNotExist:
#         messages.error(request, "Details not found")
#         return redirect("units:create")
#
#     context['data'] = data
#     return render(request, 'unit/detail.html', context)

def delete(request, pk):
    try:
        data = Unit.objects.get(id=pk)
    except Unit.DoesNotExist:
        messages.error(request, "Data not found")
        return redirect("units:list")
    data.delete()
    messages.success(request, 'Deleted successfully.')
    return redirect("units:list")