import pandas as pd
from tkinter.messagebox import showinfo
import numpy as np
import os
import time
import re
import pdfplumber
import openpyxl

def Extraer_PDF_info(PDFpath: str):
    '''
    Extrae los Datos de todos de las facturas PDF de una carpeta que se descargaron del servicio Comprobantes en Línea de AFIP

    Parameters
    ----------
    path : str
        Path de la carpeta donde se encuentran los PDF de las facturas
    '''

    directorio = PDFpath

    Start = time.time()

    # Listar todos los archivos del directorio y sus subdirectorios en una lista sin backslash
    lista_archivos = os.listdir(directorio)

    # Filtrar la lista para que solo queden los archivos PDF
    lista_archivos = [i for i in lista_archivos if i.endswith(".pdf")]

    # Agregar el directorio a cada archivo de la lista
    lista_archivos = [directorio + "/" + i for i in lista_archivos]

    # Cambiar los backslash por slash
    lista_archivos = [i.replace("\\", "/") for i in lista_archivos]

    # Eliminar de la lista los PDF que pesen menos de 10 KB
    lista_archivos = [i for i in lista_archivos if os.path.getsize(i) > 10000]

    # Crear un dataframe vacío
    df = pd.DataFrame(columns=["Archivo PDF", "CUIT del Emisor" , "COD" , "Punto de Venta", "Número de Factura", "Fecha", "Desde" , "Hasta" , "AUX"])

    # Extraer el texto de los archivos PDF solamente de la primera página

    # si no hay elementos en la lista, no hacer nada
    if len(lista_archivos) > 0:

        for i in lista_archivos:
            with pdfplumber.open(i) as pdf:
                primera_pagina = pdf.pages[0]
                texto = primera_pagina.extract_text()
                #print(texto)
                #print("--------------------------------------------------")

                Archivo = i.split("/")[-1].replace(".pdf", "")

                # Extraer el COD de de factura (Puede aparecer como "COD." o "CÓD.")
                Cod1 = re.search(r"(CÓD.)(\s)*(\d+)", texto)
                Cod2 = re.search(r"(COD.)(\s)*(\d+)", texto)
                if Cod1 == None:
                    Cod = Cod2.group(3)
                else:
                    Cod = Cod1.group(3)
                #print(Cod)

                # Extraer primer match el CUIT del emisor
                CUIT = re.search(r"CUIT: (\d+)", texto)
                CUIT = CUIT.group(1)
                #print(CUIT)

                # Extraer el punto de venta
                punto_venta = re.search(r"Punto de Venta: (\d+)", texto)
                punto_venta = punto_venta.group(1)
                #print(punto_venta)

                # Extraer el número de factura
                numero_factura = re.search(r"Comp. Nro: (\d+)", texto)
                numero_factura = numero_factura.group(1)
                #print(numero_factura)

                # Extraer la fecha
                fecha = re.search(r"Fecha de Emisión: (\d+/\d+/\d+)", texto)
                fecha = fecha.group(1)
                #print(fecha)

                # Extraer el rango de facturas, si no existe se deja vacío
                Desde = re.search(r"Desde: (\d+/\d+/\d+)", texto)
                if Desde == None:
                    Desde = ""
                else:
                    Desde = Desde.group(1)
                #print(Desde)
                Hasta = re.search(r"Hasta:(\d+/\d+/\d+)", texto)
                if Hasta == None:
                    Hasta = ""
                else:
                    Hasta = Hasta.group(1)
                #print(Hasta)

                # Agregar una linea nueva con los datos extraidos
                df = pd.concat([df, pd.DataFrame([[Archivo, Cod, CUIT, punto_venta, numero_factura, fecha, Desde, Hasta]], columns=["Archivo PDF", "COD" , "CUIT del emisor" , "Punto de Venta", "Número de Factura", "Fecha", "Desde" , "Hasta"])], ignore_index=True)

        # Transformar las columnas 'COD' , 'CUIT del emisor' , 'Punto de Venta' y 'Número de Factura' a int
        df["COD"] = df["COD"].astype(int)
        df["CUIT del emisor"] = df["CUIT del emisor"].astype(np.int64)
        df["Punto de Venta"] = df["Punto de Venta"].astype(int)
        df["Número de Factura"] = df["Número de Factura"].astype(int)

        # Crear una columna 'AUX' con el 'COD' , 'CUIT del emisor' , el 'Punto de Venta' y 'Número de Factura'
        df["AUX"] = df["COD"].astype(str) + "-" + df["Punto de Venta"].astype(str) + "-" + df["Número de Factura"].astype(str) + "-" + df["CUIT del emisor"].astype(str)

        # Exportar el dataframe a un archivo csv
        df.to_excel("Datos de Facturas PDF.xlsx", index=False)

    End = time.time()

    Tiempo_Total = End - Start

    showinfo("Extracción de datos", f"Se extrajeron los datos de {len(lista_archivos)} archivos en {Tiempo_Total} segundos")

    return df

def Control(MCpath: str , PDFPath: str):
    '''
    Controla los datos de los archivos de 'Mis Comprobantes' con las escalas de categorías de AFIP

    Parameters
    ----------
    MCpath : str
        Path de la carpeta donde se encuentran los archivos de 'Mis Comprobantes'
    PDFPath : str
        Path de la carpeta donde se encuentran los PDF de las facturas
    '''
    # Mostrar mensaje de inicio
    showinfo("Control de datos", "Se iniciará el control de los datos de los archivos de 'Mis Comprobantes' con las escalas de categorías de AFIP y los PDF de las facturas")

    # Leer Excel con las tablas de las escalas
    Categorias = pd.read_excel('Categorias.xlsx')

    # Leer la celda 'A2' de la hoja 'Rango de Fechas' y guardarla en la variable 'fecha_inicial' en formato datetime
    fecha_inicial = pd.read_excel('Categorias.xlsx', sheet_name='Rango de Fechas', header=None, skiprows=1, usecols=[0]).iloc[0,0]
    fecha_inicial = pd.to_datetime(fecha_inicial , format='%d/%m/%Y')
    # leer la celda 'B2' en fomato fecha
    fecha_final = pd.read_excel('Categorias.xlsx', sheet_name='Rango de Fechas', header=None, skiprows=1, usecols=[1]).iloc[0,0]
    fecha_final = pd.to_datetime(fecha_final , format='%d/%m/%Y')

    # Preguntar por el Excel con los Archivos de 'Mis Comprobantes'
    Archivos = os.listdir(MCpath)

    # Agregar el directorio a cada archivo de la lista
    Archivos = [MCpath + "/" + i for i in Archivos]

    # Filtrar la lista para que solo queden los archivos Excel
    Archivos = [i for i in Archivos if i.endswith(".xlsx")]

    # Crear un DataFrame vacio para guardar los datos consolidados
    Consolidado = pd.DataFrame()

    # Leer cada uno de los archivos de 'Mis Comprobantes' y concat en el DataFrame Consolidado
    # Consolidar archivos y renombrar columnas
    # consolidadar columnas
    for f in Archivos:
        #Si el existe el archivo, leerlo
        if os.path.isfile(f):
            data = pd.read_excel(f, header = None, skiprows=2 , )
            # si el datsaframe esta vacio, no hacer nada
            if len(data) > 0:

                # Crear la columna 'Archivo' con el ultimo elemento de 'f' separado por "/"
                data['Archivo'] = f.split("/")[-1]
                #data['Archivo'] = f.str.split("/")[-1]
                data['CUIT Cliente'] = data["Archivo"].str.split("-").str[3].str.strip().astype(np.int64)
                data['Fin CUIT'] = data["Archivo"].str.split("-").str[0].str.strip().astype(np.int64)
                data['Cliente'] = data["Archivo"].str.split("-").str[-1].str.strip().replace('.xlsx','', regex=True)
                # Si el df data tiene 17 columnas, entonces se debe eliminar la ultima columna
                if len(data.columns) == 17:
                    data.drop(data.columns[[-1]], axis=1, inplace=True)
                Consolidado = pd.concat([Consolidado , data])

    # Renombrar columnas
    Consolidado.columns = [ 'Fecha' , 'Tipo' , 'Punto de Venta' , 'Número Desde' , 'Número Hasta' , 'Cód. Autorización' , 'Tipo Doc. Receptor' , 'Nro. Doc. Receptor/Emisor' , 'Denominación Receptor/Emisor' , 'Tipo Cambio' , 'Moneda' , 'Imp. Neto Gravado' , 'Imp. Neto No Gravado' , 'Imp. Op. Exentas' , 'Otros Tributos' , 'IVA' , 'Imp. Total' , 'Archivo' , 'CUIT Cliente' , 'Fin CUIT' , 'Cliente']

    #Eliminar las columas 'Imp. Neto Gravado' , 'Imp. Neto No Gravado' , 'Imp. Op. Exentas' , 'IVA'
    Consolidado.drop(['Imp. Neto Gravado' , 'Imp. Neto No Gravado' , 'Imp. Op. Exentas' , 'IVA'], axis=1, inplace=True)

    #Cambiar de signo si es una Nota de Crédito
    Consolidado.loc[Consolidado["Tipo"].str.contains("Nota de Crédito"), ['Imp. Total']] *= -1

    #Crear columna de 'MC' con los valores 'archivo' que van desde el caracter 5 al 8 en la Consolidado
    Consolidado['MC'] = Consolidado['Archivo'].str.split("-").str[1].str.strip()

    Info_Facturas_PDF = Extraer_PDF_info(PDFpath=PDFPath)

    Consolidado['Tipo'] = Consolidado['Tipo'].str.split(" ").str[0].str.strip().astype(int)

    Consolidado['AUX'] = Consolidado['Tipo'].astype(str) + "-" + Consolidado['Punto de Venta'].astype(str) + "-" + Consolidado['Número Desde'].astype(str) + "-" + Consolidado['CUIT Cliente'].astype(str)

    # Merge con la tabla Info_Facturas_PDF
    Consolidado = pd.merge(Consolidado ,
                           Info_Facturas_PDF[['AUX' , 'Desde' , 'Hasta' , 'Archivo PDF']] ,
                           how='left' ,
                           left_on='AUX' ,
                           right_on='AUX')

    # Crear la columna 'Cruzado' con valores 'Si' o 'No' dependiendo si se cruzó o no la información
    Consolidado['Cruzado'] = np.nan
    Consolidado.loc[Consolidado['Archivo PDF'].notnull() , 'Cruzado'] = 'Si'
    Consolidado.loc[Consolidado['Archivo PDF'].isnull() , 'Cruzado'] = 'No'

    # Si las columnas 'Desde' y 'Hasta' son NaN entonces Eliminar todas filas donde la columna 'Fecha' no se encuentre entre el rango de fechas iniciales y finales
    ##########Consolidado = Consolidado[(Consolidado['Fecha'] >= fecha_inicial) & (Consolidado['Fecha'] <= fecha_final) & (Consolidado['Desde'].isnull()) & (Consolidado['Hasta'].isnull())]

    # Si las columnas 'Desde' y 'Hasta' son vacíos entonces toman el valor de 'Fecha'
    Consolidado['Desde'] = Consolidado['Desde'].fillna(Consolidado['Fecha'])
    Consolidado['Hasta'] = Consolidado['Hasta'].fillna(Consolidado['Fecha'])
    Consolidado.loc[Consolidado['Desde'] == "", 'Desde'] = Consolidado['Fecha']
    Consolidado.loc[Consolidado['Hasta'] == "", 'Hasta'] = Consolidado['Fecha']

    # Transformar las columnas 'FECHA EMISION' y 'HASTA' en formato datetime
    Consolidado['Desde'] = pd.to_datetime(Consolidado['Desde'], format='%d/%m/%Y')
    Consolidado['Hasta'] = pd.to_datetime(Consolidado['Hasta'], format='%d/%m/%Y')
    Consolidado['Fecha'] = pd.to_datetime(Consolidado['Fecha'], format='%d/%m/%Y')

    # Crear columna auxiliar en Consolidado con el máximo entre la variable fecha_inicial y la columna 'FECHA EMISION'
    Consolidado['Fecha Inicial'] = fecha_inicial
    Consolidado['Fecha_Inicial_max'] = Consolidado[['Fecha Inicial' , 'Desde']].max(axis=1)
    del Consolidado['Fecha Inicial']

    # Crear columna auxiliar en Consolidado con el mínimo entre la variable fecha_final y la columna 'HASTA'
    Consolidado['Fecha Final'] = fecha_final
    Consolidado['Fecha_Final_min'] = Consolidado[['Fecha Final' , 'Hasta']].min(axis=1)
    del Consolidado['Fecha Final']

    # Calcular los dias de diferencia entre 'FECHA EMISION' y 'HASTA'
    Consolidado['Dias de facturación'] = Consolidado['Hasta'] - Consolidado['Desde']
    Consolidado['Dias de facturación'] = Consolidado['Dias de facturación'].dt.days +1

    # Crear una columa 'Días Efectivos' como la diferencia entre la columna 'Fecha_Final_min' y la columna 'Fecha_Inicial_max'
    Consolidado['Días Efectivos'] = Consolidado['Fecha_Final_min'] - Consolidado['Fecha_Inicial_max']
    Consolidado['Días Efectivos'] = Consolidado['Días Efectivos'].dt.days +1

    # si los 'Días Efectivos' son menores a 0, entonces se reemplaza por 0
    Consolidado.loc[Consolidado['Días Efectivos'] < 0, 'Días Efectivos'] = 0

    # Crear una columna 'Importe por día' con el valor de la columna 'Imp. Total' dividido entre el valor de la columna 'Dias de facturación'
    Consolidado['Importe por día'] = Consolidado['Imp. Total'] / Consolidado['Dias de facturación']

    # Multiplicar el valor de la columna 'Importe por día' por el valor de la columna 'Días Efectivos' y guardar el resultado en la columna 'Importe Prorrateado'
    Consolidado['Importe Prorrateado'] = Consolidado['Importe por día'] * Consolidado['Días Efectivos']

    # Volver a mostrar las columnas 'Desde', 'Hasta' y 'Fecha' en formato fecha
    Consolidado['Desde'] = Consolidado['Desde'].dt.strftime('%d/%m/%Y')
    Consolidado['Hasta'] = Consolidado['Hasta'].dt.strftime('%d/%m/%Y')
    Consolidado['Fecha'] = Consolidado['Fecha'].dt.strftime('%d/%m/%Y')
    Consolidado['Fecha_Inicial_max'] = Consolidado['Fecha_Inicial_max'].dt.strftime('%d/%m/%Y')
    Consolidado['Fecha_Final_min'] = Consolidado['Fecha_Final_min'].dt.strftime('%d/%m/%Y')

    No_Cruzado = 0

    if 'No' in Consolidado['Cruzado'].values:
        No_Cruzado = Consolidado['Cruzado'].value_counts()['No']

    #Crear Tabla dinámica con los totales de las columnas  'Importe Prorrateado' por 'Archivo'
    TablaDinamica = pd.pivot_table(Consolidado, values=['Importe Prorrateado' , 'Tipo'], index=['Cliente' , 'MC'], aggfunc={'Importe Prorrateado': np.sum , 'Tipo': 'count'})

    # Renombrar la columna 'Tipo' por 'Cantidad de Comprobantes' de la TablaDinamica1 , TablaDinamica2 y TablaDinamica3
    TablaDinamica.rename(columns={'Tipo': 'Cantidad de Comprobantes'}, inplace=True)

    # Buscar el valor de 'Importe Prorrateado' en la escala de categorias donde el valor esta en 'Ingresos brutos'
    TablaDinamica['Ingresos brutos máximos por la categoría'] = TablaDinamica['Importe Prorrateado'].apply(lambda x: Categorias.loc[Categorias['Ingresos brutos'] >= x, 'Ingresos brutos'].iloc[0])

    #Buscar la 'Categoría' en la escala de categorias donde el valor esta en 'Ingresos brutos máximos por la categoría'
    TablaDinamica['Categoría'] = TablaDinamica['Importe Prorrateado'].apply(lambda x: Categorias.loc[Categorias['Ingresos brutos'] >= x, 'Categoria'].iloc[0])

    # Exportar el Consolidado y la Tabla Dinámica a un archivo de Excel
    Archivo_final = pd.ExcelWriter('Reporte Recategorizaciones de Monotributistas.xlsx', engine='openpyxl')
    TablaDinamica.to_excel(Archivo_final, sheet_name='Tabla Dinámica', index=True)
    Consolidado.to_excel(Archivo_final, sheet_name='Consolidado', index=False)
    Archivo_final.close()

    #Guardar el archivo
    #Archivo_final.save()

    #Mostrar mensaje de finalización
    showinfo(title="Finalizado", message=f"El archivo se ha generado correctamente.\n \nCantidad de Facturas no cruzados: {No_Cruzado}")

if __name__ == "__main__":
    Control()


import math
import os
from helpers.logger import Logger

import torch
import torch.nn as nn
from transformers import BertModel
from sklearn import metrics
from torch.utils.data import DataLoader, random_split

from data_utils import (ABSADataset, Tokenizer4Bert, build_embedding_matrix,
                        build_tokenizer)

torch.set_printoptions(profile="full")

class ModelRunner:
    def __init__(self, opt):

        self.opt = opt
        self.logger = Logger(opt.model_name, opt.dataset)

        if 'bert' in opt.model_name:
            tokenizer = Tokenizer4Bert(
                opt.max_seq_len,
                opt.pretrained_bert_name)
            bert = BertModel.from_pretrained(opt.pretrained_bert_name)
            self.model = opt.model_class(bert, opt).to(opt.device)
        else:
            tokenizer = build_tokenizer(
                fnames=[opt.dataset_file['train'], opt.dataset_file['test']],
                max_seq_len=opt.max_seq_len,
                dat_fname='{0}_tokenizer.dat'.format(opt.dataset))
            embedding_matrix = build_embedding_matrix(
                word2idx=tokenizer.word2idx,
                embed_dim=opt.embed_dim,
                dat_fname='{0}_{1}_embedding_matrix.dat'.format(str(opt.embed_dim), opt.dataset))
            self.model = opt.model_class(embedding_matrix, opt).to(opt.device)

        self.trainset = ABSADataset(opt.dataset_file['train'], tokenizer)
        self.testset = ABSADataset(opt.dataset_file['test'], tokenizer)
        assert 0 <= opt.valset_ratio < 1
        if opt.valset_ratio > 0:
            valset_len = int(len(self.trainset) * opt.valset_ratio)
            self.trainset, self.valset = random_split(self.trainset, (len(self.trainset)-valset_len, valset_len))
        else:
            self.valset = self.testset

        if opt.device.type == 'cuda':
            self.logger.log('cuda memory allocated: {}'.format(torch.cuda.memory_allocated(device=opt.device.index)))
        self._print_args()

    def _print_args(self):
        n_trainable_params, n_nontrainable_params = 0, 0
        for p in self.model.parameters():
            n_params = torch.prod(torch.tensor(p.shape))
            if p.requires_grad:
                n_trainable_params += n_params
            else:
                n_nontrainable_params += n_params
        self.logger.log('n_trainable_params: {0}, n_nontrainable_params: {1}'.format(n_trainable_params, n_nontrainable_params))
        self.logger.log('> training arguments:')
        for arg in vars(self.opt):
            self.logger.log('>>> {0}: {1}'.format(arg, getattr(self.opt, arg)))

    def _reset_params(self):
        for child in self.model.children():
            if type(child) != BertModel:  # skip bert params
                for p in child.parameters():
                    if p.requires_grad:
                        if len(p.shape) > 1:
                            self.opt.initializer(p)
                        else:
                            stdv = 1. / math.sqrt(p.shape[0])
                            torch.nn.init.uniform_(p, a=-stdv, b=stdv)

    def _train(self, criterion, optimizer, train_data_loader, val_data_loader):
        max_val_acc = 0
        max_val_f1 = 0
        global_step = 0
        path = None

        tmp_best_model = None
        best_path = None

        for epoch in range(self.opt.num_epoch):
            # self.logger.log('>' * 100)
            self.logger.log('epoch: {}'.format(epoch))
            n_correct, n_total, loss_total = 0, 0, 0
            # switch model to training mode
            self.model.train()
            for i_batch, sample_batched in enumerate(train_data_loader):
                global_step += 1
                # clear gradient accumulators
                optimizer.zero_grad()

                inputs = [sample_batched[col].to(self.opt.device) for col in self.opt.model_inputs]
                outputs = self.model(inputs)
                targets = sample_batched['polarity'].to(self.opt.device)

                loss = criterion(outputs, targets)
                loss.backward()
                optimizer.step()

                n_correct += (torch.argmax(outputs, -1) == targets).sum().item()
                n_total += len(outputs)
                loss_total += loss.item() * len(outputs)
                if global_step % self.opt.log_step == 0:
                    train_acc = n_correct / n_total
                    train_loss = loss_total / n_total
                    logstr = 'loss: {:.4f}, acc: {:.4f}'.format(
                      train_loss, train_acc)
                    print(logstr)
                    # self.logger.log(logstr)

            val_acc, val_f1 = self._evaluate_acc_f1(val_data_loader)
            self.logger.log('> val_acc: {:.4f}, val_f1: {:.4f}'.format(val_acc, val_f1))
            # if val_acc > max_val_acc:
                # max_val_acc = val_acc
            if val_f1 > max_val_f1:
                max_val_f1 = val_f1
                self.logger.log("New best model found, F1 = {}".format(
                  max_val_f1))
                if not os.path.exists('state_dict'):
                    os.mkdir('state_dict')

                best_path = 'state_dict/{0}_{1}_val_acc{2}_val_f{3}.tmp'.format(self.opt.model_name, self.opt.dataset, round(val_acc, 4), round(val_f1, 4))
                tmp_best_model = self.model.state_dict()

        # if tmp_best_model:
        #     torch.save(tmp_best_model, best_path)
        #     self.logger.log('>> saved: {}'.format(best_path))

        #return best_path
        return tmp_best_model

    def _evaluate_acc_f1(self, data_loader):
        n_correct, n_total = 0, 0
        t_targets_all, t_outputs_all = None, None
        # switch model to evaluation mode
        self.model.eval()
        with torch.no_grad():
            for t_batch, t_sample_batched in enumerate(data_loader):
                t_inputs = [t_sample_batched[col].to(self.opt.device) for col in self.opt.model_inputs]
                t_targets = t_sample_batched['polarity'].to(self.opt.device)
                t_outputs = self.model(t_inputs)

                n_correct += (torch.argmax(t_outputs, -1) == t_targets).sum().item()
                n_total += len(t_outputs)

                if t_targets_all is None:
                    t_targets_all = t_targets
                    t_outputs_all = t_outputs
                else:
                    t_targets_all = torch.cat((t_targets_all, t_targets), dim=0)
                    t_outputs_all = torch.cat((t_outputs_all, t_outputs), dim=0)

        acc = n_correct / n_total
        cpu_tar = t_targets_all.cpu()
        cpu_out = torch.argmax(t_outputs_all.cpu(), -1).cpu()
        self.logger.log("True: {}".format(cpu_tar))
        self.logger.log("Predicted: {}".format(cpu_out))
        f1 = metrics.f1_score(cpu_tar, cpu_out, labels=[0, 1, 2], average='macro')
        return acc, f1

    def run(self):
        # Loss and Optimizer
        criterion = nn.CrossEntropyLoss()
        _params = filter(lambda p: p.requires_grad, self.model.parameters())
        optimizer = self.opt.optimizer(_params, lr=self.opt.learning_rate, weight_decay=self.opt.l2reg)

        train_data_loader = DataLoader(dataset=self.trainset, batch_size=self.opt.batch_size, shuffle=True)
        test_data_loader = DataLoader(dataset=self.testset, batch_size=self.opt.batch_size, shuffle=False)
        val_data_loader = DataLoader(dataset=self.valset, batch_size=self.opt.batch_size, shuffle=False)

        self._reset_params()
        # best_model_path = self._train(criterion, optimizer, train_data_loader, val_data_loader)
        # self.model.load_state_dict(torch.load(best_model_path))

        # return best state dict
        best_model = self._train(
          criterion,
          optimizer,
          train_data_loader,
          val_data_loader
        )
        self.model.load_state_dict(best_model)
        self.model.eval()
        test_acc, test_f1 = self._evaluate_acc_f1(test_data_loader)
        self.logger.log(
          '>> test_acc: {:.4f}, test_f1: {:.4f}'.format(test_acc, test_f1)
        )
        save_name = 'state_dict/{}_ACC{}_F{}.{}'.format(
          self.opt.dataset,
          round(test_acc, 4),
          round(test_f1, 4),
          self.opt.model_name
        )
        # torch.save(self.model.state_dict(), save_name)


import numpy as np
from math import cos, sin, pi
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import imageio
from scipy.interpolate import griddata
from os.path import join
import os,sys,inspect
from pathlib import Path
import pandas as pd
import seaborn as sns

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)

# print(prob_path)
class plot_environment_fields:
    def __init__(self, prob_path):
        self.all_u_mat = np.load(join(prob_path, "all_u_mat.npy"))
        self.all_ui_mat = np.load(join(prob_path, "all_ui_mat.npy"))
        self.all_v_mat = np.load(join(prob_path, "all_v_mat.npy"))
        self.all_vi_mat = np.load(join(prob_path, "all_vi_mat.npy"))
        self.all_Yi = np.load(join(prob_path, "all_Yi.npy"))
        self.all_s_mat = np.load(join(prob_path, "all_s_mat.npy"))
        self.obstacle_mask = np.load(join(prob_path, "obstacle_mask.npy"))
        try:
            self.all_phi_mat = np.load(join(prob_path, "all_phi_mat.npy"))
            self.all_phi_i_mat = np.load(join(prob_path, "all_phi_i_mat.npy"))
            self.all_phi_Yi_mat = np.load(join(prob_path, "all_phi_Yi_mat.npy"))
            print(self.all_phi_i_mat.shape)
        except:
            pass
        print("all_Yi.shape =",self.all_Yi.shape)
        print("all_s_mat.shape =",self.all_s_mat.shape)

        self.nt, self.nmodes, self.gsize, _ = self.all_ui_mat.shape
        self.dxy = 2/self.gsize
        self.xs = np.array([(0.5+i)*self.dxy for i in range(self.gsize)])
        self.ys = self.xs
        self.X, self.Y = np.meshgrid(self.xs, np.flip(self.ys))
        self.prob_path = prob_path
        self.num_fontsize = 20
        self.strmplot_arrowsize  = 3.0 # scaling factor
        self.strmplot_lw = 2 # linewidth of streamplot

    def plot_modes(self, t, show_contours="mode_vel_mag", show_colorbar=False, show_fig=True, save_fig=False):
        # t is the time index at which the plot is made
        fig = plt.figure(figsize=(10, 10))
        gs1 = gridspec.GridSpec(2, 2)
        gs1.update(wspace=0.05, hspace=0.15) # set the spacing between axes.

        for k in range(4): #hardcoded for first 4 modes
            # ax = fig.add_subplot(2, 2, k+1)
            title = "Mode " + str(k+1)
            ax = plt.subplot(gs1[k])
            ax.axes.xaxis.set_visible(False)
            ax.axes.yaxis.set_visible(False)
            ax.axes.set_title(title, fontsize=22)
            plt.streamplot(self.X, self.Y, self.all_ui_mat[t,k,:,:], self.all_vi_mat[t,k,:,:],color='k',
                            linewidth=self.strmplot_lw, arrowsize=0.5*self.strmplot_arrowsize)
            if show_contours == "phi":
                plt.contourf(self.X, self.Y, self.all_phi_i_mat[t,k,:,:], cmap='bwr')
            if show_contours == "mode_vel_mag":
                u_t_k = self.all_ui_mat[t,k,:,:]
                v_t_k = self.all_vi_mat[t,k,:,:]
                vel_mag_t_k = (u_t_k**2 + v_t_k**2)**0.5 #velocity magnitude at t,k
                plt.contourf(self.X, self.Y, vel_mag_t_k, cmap='Blues')
            if show_colorbar==True:
                cbar = plt.colorbar()
                cbar.ax.tick_params(labelsize=self.num_fontsize)

        if save_fig:
            plt.savefig(join(self.prob_path, "modes@") + str(t),bbox_inches = "tight", dp = 300)
        if show_fig:
            plt.show()

    def plot_mean_vel_field(self, t, k,  show_obstacle=False,
                                    show_streams = True,
                                    show_contours = None,
                                    show_colorbar = False,
                                    show_fig = True,
                                    save_fig = False):
        # t is the time index at which the plot is made
        # k is the rzn index

        fig = plt.figure(figsize=(10, 10))
        ax = fig.add_subplot(1, 1, 1)
        cb=''
        self.setup_grid_in_plot(fig, ax)

        u_t_k = self.all_u_mat[t,:,:]
        v_t_k = self.all_v_mat[t,:,:]
        try:
            phi_t_k = self.all_phi_mat[t,:,:]
        except:
            pass

        if show_streams:
            plt.streamplot(self.X, self.Y, u_t_k, v_t_k,color='k', linewidth=self.strmplot_lw, arrowsize=self.strmplot_arrowsize)
        if show_contours != None:
            if show_contours == "phi":
                plt.contourf(self.X, self.Y, phi_t_k, cmap='bwr')
            if show_contours == "vel_mag":
                vel_mag_t_k = (u_t_k**2 + v_t_k**2)**0.5 #velocity magnitude at t,k
                plt.contourf(self.X, self.Y, vel_mag_t_k, cmap = 'Blues')
        if show_colorbar:
            cbar = plt.colorbar()
            cbar.ax.tick_params(labelsize=self.num_fontsize)
            cb = 'wcb'
        if show_obstacle:
            self.plot_obstacle(t)

        if save_fig:
            plt.savefig(join(self.prob_path, "mean_vel_field_") + cb + "@" + str(t) + "_" + str(k),bbox_inches = "tight", dp = 300)
        if show_fig:
            plt.show()

    def plot_vel_field(self, t, k,  show_obstacle=False,
                                    show_streams = True,
                                    show_contours = None,
                                    show_colorbar = False,
                                    show_fig = True,
                                    save_fig = False):
        # t is the time index at which the plot is made
        # k is the rzn index

        fig = plt.figure(figsize=(10, 10))
        ax = fig.add_subplot(1, 1, 1)
        matmul_ui_Yi_t = 0
        matmul_vi_Yi_t = 0
        matmul_phi_i_phi_Yi_t = 0
        cb=''
        self.setup_grid_in_plot(fig, ax)

        myticks = [0, 25, 50, 75, 100]
        xyticks = np.array([0, 0.5, 1.0, 1.5, 2.0])
        plt.xticks(xyticks, myticks, fontsize=30)
        plt.yticks(xyticks, myticks, fontsize=30)

        for m in range(self.nmodes):
            matmul_ui_Yi_t += (self.all_ui_mat[t,m,:,:]*self.all_Yi[t,k,m])
            matmul_vi_Yi_t += (self.all_vi_mat[t,m,:,:]*self.all_Yi[t,k,m])
            u_t_k = self.all_u_mat[t,:,:] + matmul_ui_Yi_t
            v_t_k = self.all_v_mat[t,:,:] + matmul_vi_Yi_t
            try:
                matmul_phi_i_phi_Yi_t += (self.all_phi_i_mat[t,m,:,:]*self.all_phi_Yi_mat[t,k,m])
                phi_t_k = self.all_phi_mat[t,:,:] + matmul_phi_i_phi_Yi_t
            except:
                pass

        if show_streams:
            plt.streamplot(self.X, self.Y, u_t_k, v_t_k, color='k', linewidth=self.strmplot_lw, arrowsize=self.strmplot_arrowsize)
        if show_contours != None:
            if show_contours == "phi":
                plt.contourf(self.X, self.Y, phi_t_k, cmap='bwr')
            if show_contours == "vel_mag":
                vel_mag_t_k = (u_t_k**2 + v_t_k**2)**0.5 #velocity magnitude at t,k
                plt.contourf(self.X, self.Y, vel_mag_t_k, cmap = 'Blues')
        if show_colorbar:
            cbar = plt.colorbar()
            cbar.ax.tick_params(labelsize=self.num_fontsize)
            cb = 'wcb'
        if show_obstacle:
            self.plot_obstacle(t)

        if save_fig:
            plt.savefig(join(self.prob_path, "vel_field_") + cb + "@" + str(t) + "_" + str(k),bbox_inches = "tight", dp = 300)
        if show_fig:
            plt.show()

    def plot_scalar_field(self, t, k, show_obstacle=False,
                                        show_colorbar=False,
                                        show_fig=True,
                                        save_fig=False):

        fig = plt.figure(figsize=(10, 10))
        ax = fig.add_subplot(1, 1, 1)
        self.setup_grid_in_plot(fig, ax)
        cb =''
        plt.contourf(self.X, self.Y, self.all_s_mat[t,:,:], cmap = "YlOrRd_r", alpha = 0.5)
        if show_obstacle:
            self.plot_obstacle(t)
        if show_colorbar:
            cbar = plt.colorbar()
            cbar.ax.tick_params(labelsize=self.num_fontsize)
            cb = 'wcb'
        if save_fig:
            plt.savefig(join(self.prob_path, "scalar_field_") + cb + "@" + str(t) + "_" + str(k),bbox_inches = "tight",dp = 300)
        if show_fig:
            plt.show()

    def get_cell_corners(self,t,i,j):
        xc = self.xs[j]
        yc = self.ys[self.gsize - 1 - i]
        xl = xc - self.dxy/2
        xr = xc + self.dxy/2
        yt = yc + self.dxy/2
        yb = yc - self.dxy/2
        corner_x_coords = [xl, xr, xr, xl]
        corner_y_coords = [yt, yt, yb, yb]
        return corner_x_coords, corner_y_coords

    def plot_obstacle(self, t):
        # fig = plt.figure(figsize=(10, 10))
        # ax = fig.add_subplot(1, 1, 1)
        for i in range(self.gsize):
            for j in range(self.gsize):
                if self.obstacle_mask[t,i,j] == 1:
                    x_corners, y_corners = self.get_cell_corners(t,i,j)
                    plt.fill(x_corners, y_corners, 'dimgrey', alpha = 1, zorder = 1e6)
        # plt.show()
        return

    def plot_all(self,t,k, show_fig=True, save_fig=False):
        fig = plt.figure(figsize=(10, 10))
        ax = fig.add_subplot(1, 1, 1)
        self.setup_grid_in_plot(fig, ax)

        plt.contourf(self.X, self.Y, self.all_s_mat[t,:,:], cmap = "YlOrRd_r", alpha = 0.5)
        matmul_ui_Yi_t = 0
        matmul_vi_Yi_t = 0
        matmul_phi_i_phi_Yi_t = 0

        for m in range(self.nmodes):
            matmul_ui_Yi_t += (self.all_ui_mat[t,m,:,:]*self.all_Yi[t,k,m])
            matmul_vi_Yi_t += (self.all_vi_mat[t,m,:,:]*self.all_Yi[t,k,m])
        u_t_k = self.all_u_mat[t,:,:] + matmul_ui_Yi_t
        v_t_k = self.all_v_mat[t,:,:] + matmul_vi_Yi_t

        try:
            matmul_phi_i_phi_Yi_t += (self.all_phi_i_mat[t,m,:,:]*self.all_phi_Yi_mat[t,k,m])
            phi_t_k = self.all_phi_mat[t,:,:] + matmul_phi_i_phi_Yi_t
        except:
            pass

        plt.streamplot(self.X, self.Y, u_t_k, v_t_k,color='k', linewidth=self.strmplot_lw, arrowsize=self.strmplot_arrowsize)
        self.plot_obstacle(t)
        if save_fig:
            plt.savefig(join(self.prob_path, "environment@") + str(t) + "_" + str(k),bbox_inches = "tight", dp =300)
        if show_fig:
            plt.show()

    def plot_env_gif(self):
        fig = plt.figure(figsize=(10, 10))
        ax = fig.add_subplot(1, 1, 1)
        # self.setup_grid_in_plot(fig, ax)
        images = []
        fname = "last_fig_in_gif.png"

        for t in range(self.nt):
            plt.contourf(self.X, self.Y, self.all_s_mat[t,:,:], cmap = "YlOrRd_r", alpha = 0.5)
            self.plot_obstacle(t)
            plt.savefig(fname)
            plt.clf()
            images.append(imageio.imread(fname))
        imageio.mimsave(join(self.prob_path, 'scalar_field_dynObs.gif'), images, duration = 0.5)

    def plot_env_sequence(self, k, plot_interval, show_contours = "vel_mag"):

        plot_seq_path = join(self.prob_path, "plot_sequence_env")
        Path(plot_seq_path).mkdir(parents=False, exist_ok=True)
        print("plotting env sequence at tid:")
        for t in range(self.nt):
            if (t%plot_interval == 0 or t == self.nt -1):
                fig = plt.figure(figsize=(10, 10))
                ax = fig.add_subplot(1, 1, 1)
                self.setup_grid_in_plot(fig, ax)
                # title = "bg_"+ show_contours+ " t = " + str(t)
                #title = " t = " + str(t)
                #plt.title(title, fontsize=30)

                print(t, end='\t')
                matmul_ui_Yi_t = 0
                matmul_vi_Yi_t = 0
                matmul_phi_i_phi_Yi_t = 0

                for m in range(self.nmodes):
                    matmul_ui_Yi_t += (self.all_ui_mat[t,m,:,:]*self.all_Yi[t,k,m])
                    matmul_vi_Yi_t += (self.all_vi_mat[t,m,:,:]*self.all_Yi[t,k,m])

                u_t_k = self.all_u_mat[t,:,:] + matmul_ui_Yi_t
                v_t_k = self.all_v_mat[t,:,:] + matmul_vi_Yi_t

                try:
                    matmul_phi_i_phi_Yi_t += (self.all_phi_i_mat[t,m,:,:]*self.all_phi_Yi_mat[t,k,m])
                # phi_t_k = self.all_phi_mat[t,:,:] + matmul_phi_i_phi_Yi_t
                except:
                    pass

                plt.streamplot(self.X, self.Y, u_t_k, v_t_k,color='k', linewidth=self.strmplot_lw, arrowsize=self.strmplot_arrowsize, arrowstyle='->')
                if show_contours != None:
                    if show_contours == "scalar_field":
                        plt.contourf(self.X, self.Y, self.all_s_mat[t,:,:], cmap = "YlOrRd_r", alpha = 0.5)
                    if show_contours == "vel_mag":
                        vel_mag_t_k = (u_t_k**2 + v_t_k**2)**0.5 #velocity magnitude at t,k
                        plt.contourf(self.X, self.Y, vel_mag_t_k, cmap = 'Blues')
                #cbar = plt.colorbar()
                #cbar.ax.tick_params(labelsize=self.num_fontsize)

                myticks = [0, 25, 50, 75, 100]
                xyticks = np.array([0, 0.5, 1.0, 1.5, 2.0])

                plt.xticks(xyticks, myticks, fontsize=30)
                plt.yticks(xyticks, myticks, fontsize=30)

                self.plot_obstacle(t)
                fname_pfx = show_contours+ "env"
                fname = join(plot_seq_path, fname_pfx) + "_new_" + "@t" + str(t) + ".png"
                plt.savefig(fname,bbox_inches = "tight", dpi =300)
                plt.clf()
                plt.close()

    def setup_grid_in_plot(self, fig, ax):
        ax.set_xlim(0,self.xs[-1] + (self.dxy/2))
        ax.set_ylim(0,self.ys[-1] + (self.dxy/2))

        minor_ticks = [i*self.dxy/1 for i in range(0, self.gsize + 1, 10)]
        major_ticks = [i*self.dxy/1 for i in range(0, self.gsize + 1, 40)]
        myticks = [0, 25, 50, 75, 100]
        xyticks = np.array([0, 0.5, 1.0, 1.5, 2.0])

        #ax.xticks(xyticks, myticks)
        #ax.xticks(xyticks, myticks)

        #ax.set_xticks(minor_ticks, minor=True)
        #ax.set_xticks(major_ticks, minor=False)
        #ax.set_yticks(major_ticks, minor=False)
        #ax.set_yticks(minor_ticks, minor=True)

        # ax.grid(b= True, which='both', color='#CCCCCC', axis='both',linestyle = '-', alpha = 0.5, zorder = -1e6)
        ax.tick_params(axis='both', which='both', labelsize=self.num_fontsize)

        ax.set_xlabel('X (Non-Dim)', fontsize=30)
        ax.set_ylabel('Y (Non-Dim)', fontsize=30)

    def plot_coefs(self, rzn_list, save_fig= True, show_fig=True):
        # rzn_list is a list of rzn_ids for which you want to plot the coeffs
        fig = plt.figure(figsize=(40, 10))

        colors = ['b','g','r','y','c']
        # linestyles = ['-', '--', ':', '-.']
        labels = ['1st', '2nd', '3rd', '4th', '5th']
        labels = [label + ' mode' for label in labels]
        for i in range(len(rzn_list)):
            ax = fig.add_subplot(2, 4, i+1)
            rzn_id = rzn_list[i]
            for m in range(4):
                ax.plot(self.all_Yi[:,rzn_id, m], color = colors[m],label = labels[m], linewidth=4)
                ax.legend(fontsize = 16)
                # ax.set_title('rzn id = ' + str(rzn_id))
                ax.tick_params(axis='both', which='both', labelsize=self.num_fontsize)
                ax.set_xlabel('t', fontsize=22)
                ax.set_ylabel('Y', fontsize=22)
                # ax.xaxis.set_label_coords(1.05, -0.025)
                # title = 'rzn_id = ' + str(i)
                # ax.set_title(title,fontsize = 22)
        if save_fig == True:
            plt.savefig(join(self.prob_path, "coeffs"), bbox_inches = "tight", dpi = 300)
            plt.clf()
            plt.close()
        if show_fig == True:
            plt.show()

    def coeff_pair_plots(self,t, save_fig=True, show_fig=False):
        coeffs = self.all_Yi[t,:,0:4]
        df = pd.DataFrame(coeffs, columns = ['Coef. 1','Coef. 2','Coef. 3','Coef. 4'])
        sns.set_context("talk", rc={"font.size":10,"axes.titlesize":10,"axes.labelsize":25})

        g= sns.pairplot(df, corner=True, diag_kind="kde", plot_kws=dict(s=20, edgecolor= 'b', linewidth=0.5, alpha=0.1))
        # g.map_lower(sns.kdeplot, levels=4, color=".2")
        # g.set(xlim=(-3, 3))
        # g.set(ylim=(-3, 3))
        for ax in g.axes[:,0]:
            ax.get_yaxis().set_label_coords(-0.31,0.5)
            # ax.set_xlim((-3,3))
            # ax.set_ylim((-3,3))

        fname = join(self.prob_path, "coeff_pairplots@") + str(t)
        if save_fig==True:
            plt.savefig(fname, dpi = 300)
            plt.clf()
            plt.close()
        if show_fig==True:
            plt.show()

prob_name = "DG3_g100x100x120_r5k_vmax6_2LpDynObs_0.4_0.1_exp_3s_2r"

t = 1
rzn_id = 0
print("prob_name= ",prob_name)
prob_path = join(currentdir, prob_name)

plots = plot_environment_fields(prob_path)

rzn_list = [int(600)*i for i in range(8)]
# plots.plot_coefs(rzn_list, save_fig=True)

# for t in [i for i in range(1,150,40)]:
for t in [0, 40, 80 , 119]:
    # plots.coeff_pair_plots(t, save_fig=True, show_fig=False)
#    plots.plot_modes(t,save_fig=True, show_fig=False, show_colorbar=True)
    plots.plot_vel_field(t, rzn_id, show_contours="vel_mag", show_colorbar=False, show_obstacle=True, save_fig=True, show_fig=False)
#     plots.plot_mean_vel_field(t, rzn_id, show_contours="vel_mag", show_colorbar=False, show_obstacle=False, save_fig=True, show_fig=False)

# # # plots.plot_modes(t,save_fig=True)

# # plots.plot_vel_field(t, rzn_id, show_contours="vel_mag", show_colorbar=True, show_obstacle=False, save_fig=True)
# # plots.plot_mean_vel_field(t, rzn_id, show_contours="vel_mag", show_colorbar=True, show_obstacle=False, save_fig=True)

# plots.plot_vel_field(t,rzn_id,show_obstacle=False, show_colorbar=True, save_fig=True)
# plots.plot_scalar_field(t,rzn_id, show_obstacle=True, show_colorbar=True, save_fig=True)
# plots.plot_scalar_field(t,rzn_id, show_obstacle=True, show_colorbar=True, save_fig=True)
# plots.plot_all(t,rzn_id, save_fig=True)
# plots.plot_obstacle(t)
# # plots.plot_env_gif()
# # plots.plot_env_sequence(rzn_id, plot_interval=30, show_contours="vel_mag")
plots.plot_env_sequence(rzn_id, plot_interval=2, show_contours="scalar_field")


import scrapy

class animecrawler(scrapy.Spider):
    name = 'anime'

    start_urls = [
        'https://myanimelist.net/anime/season'
    ]

    def parse(self, response):
        for post in response.css('div.title'):
            yield{
                'title' : post.css('.title-text h2.h2_anime_title a.link-title::text').get()
            }
        for post in response.css('div.info'):
            yield{
                'lancamento' : post.css('span.item::text')[0].get()
            }


import os
import pickle
import itertools
from copy import deepcopy
import numpy as np
import pandas as pd

from sklearn.model_selection import KFold
from scipy.optimize import differential_evolution
from tqdm.notebook import tqdm

def NMAE(y_true, y_pred):
    return np.mean(np.sum(np.abs(y_true - y_pred), axis=0) / np.sum(y_true, axis=0))

class TrendsModelSklearn():
    def __init__(self, zoo: list, seed=0):
        self.zoo = zoo
        self.seed = seed

    def prepare_label(self):
        # create mask and label
        self.mask = self.label.notnull()
        self.label = self.label.dropna()

    def gen_folds(self):
        # drop nans
        self.prepare_label()
        self.folds = list(KFold(n_splits=10, shuffle=True, random_state=self.seed).split(self.label.values))

    def get_data(self, data: pd.DataFrame, is_test=False):
        if is_test:
            return data.values
        else:
            return data.loc[self.mask, :].values, self.label.values

    def fit_model(self, model, xtrain, ytrain):

        scores = []
        models = []
        oof_pred = np.zeros(xtrain.shape[0])

        for n, (tr, te) in enumerate(self.folds):
            xtr, xte = xtrain[tr], xtrain[te]
            ytr, yte = ytrain[tr], ytrain[te]

            model_local = deepcopy(model)
            model_local.fit(xtr, ytr)
            pred = model_local.predict(xte)

            # save results
            models.append(model_local)
            scores.append(NMAE(yte, pred))
            oof_pred[te] = pred

            print('Fold {} NMAE: {:.5f}'.format(n, scores[-1]))

        print('\nNMAE mean: {:.5f}'.format(np.mean(scores)))
        print('NMAE OOF: {:.5f}\n'.format(NMAE(ytrain, oof_pred)))
        return models, oof_pred

    def fit(self, datasets: list, label: pd.Series):

        self.label = label
        self.models_trained = []
        self.oof_preds = []
        self.oof_scores = []

        # prepare folds
        self.gen_folds()

        for dataset, model in zip(datasets, self.zoo):
            # get data
            xtrain, ytrain = self.get_data(dataset)

            # train models
            models, oof_pred = self.fit_model(model, xtrain, ytrain)
            self.models_trained.append(models)
            self.oof_preds.append(oof_pred)
            self.oof_scores.append(np.round(NMAE(ytrain, oof_pred), 5))

    def save_oofs(self, names, folder):
        for n, name in enumerate(names):
            with open(folder + '/{}_oof.pickle'.format(name), 'wb') as handle:
                pickle.dump(self.oof_preds[n], handle, protocol=pickle.HIGHEST_PROTOCOL)

    def save_models(self, names, folder):
        for n, name in enumerate(names):
            with open(folder + '/{}_model.pickle'.format(name), 'wb') as handle:
                pickle.dump(self.models_trained[n], handle, protocol=pickle.HIGHEST_PROTOCOL)

    def load_models(self, file):
        # put cycle here
        # load models
        with open(file, 'rb') as handle:
            self.models_loaded = pickle.load(handle)

    def predict(self, datasets, model_names, is_blend=True):
        self.preds = {}
        for dataset, model_pool, name in zip(datasets, self.models_trained, model_names):
            # get data
            xtest = self.get_data(dataset, is_test=True)

            pool_preds = []
            for model in model_pool:
                pool_preds.append(model.predict(xtest))

            self.preds[name] = pool_preds

        self.test_pred = [np.mean(self.preds[name], axis=0) for name in model_names]
        if is_blend:
            return np.dot(self.weights, self.test_pred)
        else:
            return self.test_pred

    def blend_oof(self):

        def compute_blend(w):
            return NMAE(self.label, np.dot(w, np.array(self.oof_preds)))

        print('Correlation matrix')
        print(np.corrcoef(self.oof_preds), '\n')

        bounds = [(0, 1)]*len(self.oof_preds)
        res = differential_evolution(compute_blend, bounds, seed=0)
        ws = res.x
        self.blend = np.dot(ws, np.array(self.oof_preds))
        self.weights = ws

        print('Selected weights: ', ws)
        print('Blend NMAE: {:.5f}'.format(res.fun))
        return self.blend


import logging
from functools import update_wrapper, wraps

import flask
from flask import Response, make_response, request
from flask_cors.core import get_cors_options, set_cors_headers
from flask_pydantic_spec import Response as SpecResponse
from werkzeug.exceptions import HTTPException

from api.model.patron_auth import PatronAuthAccessToken
from api.model.time_tracking import PlaytimeEntriesPost, PlaytimeEntriesPostResponse
from core.app_server import ErrorHandler, compressible, returns_problem_detail
from core.model import HasSessionCache
from core.util.problem_detail import ProblemDetail

from .app import api_spec, app, babel
from .config import Configuration

@babel.localeselector
def get_locale():
    languages = Configuration.localization_languages()
    return request.accept_languages.best_match(languages)

@app.after_request
def print_cache(response):
    if hasattr(app, "_db") and HasSessionCache.CACHE_ATTRIBUTE in app._db.info:
        log = logging.getLogger("core.model.hassessioncache")
        for cls, cache in app._db.info[HasSessionCache.CACHE_ATTRIBUTE].items():
            log.debug(f"{cls}: {cache.stats.hits}/{cache.stats.misses} hits/misses")
    return response

@app.teardown_request
def shutdown_session(exception):
    if hasattr(app, "manager") and hasattr(app.manager, "_db") and app.manager._db:
        if exception:
            app.manager._db.rollback()
        else:
            app.manager._db.commit()

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        patron = app.manager.index_controller.authenticated_patron_from_request()
        if isinstance(patron, ProblemDetail):
            return patron.response
        elif isinstance(patron, Response):
            return patron
        else:
            return f(*args, **kwargs)

    return decorated

def allows_auth(f):
    """Decorator function for a controller method that supports both
    authenticated and unauthenticated requests.

    NOTE: This decorator might not be necessary; you can probably call
    BaseCirculationManagerController.request_patron instead.
    """

    @wraps(f)
    def decorated(*args, **kwargs):
        # Try to authenticate a patron. This will set flask.request.patron
        # if and only if there is an authenticated patron.
        app.manager.index_controller.authenticated_patron_from_request()

        # Call the decorated function regardless of whether
        # authentication succeeds.
        return f(*args, **kwargs)

    return decorated

# The allows_patron_web decorator will add Cross-Origin Resource Sharing
# (CORS) headers to routes that will be used by the patron web interface.
# This is necessary for a JS app on a different domain to make requests.
#
# This is mostly taken from the cross_origin decorator in flask_cors, but we
# can't use that decorator because we aren't able to look up the patron web
# client url configuration setting at the time we create the decorator.
def allows_patron_web(f):
    # Override Flask's default behavior and intercept the OPTIONS method for
    # every request so CORS headers can be added.
    f.required_methods = getattr(f, "required_methods", set())
    f.required_methods.add("OPTIONS")
    f.provide_automatic_options = False

    def wrapped_function(*args, **kwargs):
        if request.method == "OPTIONS":
            resp = app.make_default_options_response()
        else:
            resp = make_response(f(*args, **kwargs))

        patron_web_domains = app.manager.patron_web_domains
        if patron_web_domains:
            options = get_cors_options(
                app, dict(origins=patron_web_domains, supports_credentials=True)
            )
            set_cors_headers(resp, options)

        return resp

    return update_wrapper(wrapped_function, f)

h = ErrorHandler(app, app.config["DEBUG"])

@app.errorhandler(Exception)
@allows_patron_web
def exception_handler(exception):
    if isinstance(exception, HTTPException):
        # This isn't an exception we need to handle, it's werkzeug's way
        # of interrupting normal control flow with a specific HTTP response.
        # Return the exception and it will be used as the response.
        return exception
    return h.handle(exception)

def has_library(f):
    """Decorator to extract the library short name from the arguments."""

    @wraps(f)
    def decorated(*args, **kwargs):
        if "library_short_name" in kwargs:
            library_short_name = kwargs.pop("library_short_name")
        else:
            library_short_name = None
        library = app.manager.index_controller.library_for_request(library_short_name)
        if isinstance(library, ProblemDetail):
            return library.response
        else:
            return f(*args, **kwargs)

    return decorated

def allows_library(f):
    """Decorator similar to @has_library but if there is no library short name,
    then don't set the request library.
    """

    @wraps(f)
    def decorated(*args, **kwargs):
        if "library_short_name" in kwargs:
            library_short_name = kwargs.pop("library_short_name")
            library = app.manager.index_controller.library_for_request(
                library_short_name
            )
            if isinstance(library, ProblemDetail):
                return library.response
        else:
            library = None

        return f(*args, **kwargs)

    return decorated

def library_route(path, *args, **kwargs):
    """Decorator to creates routes that have a library short name in either
    a subdomain or a url path prefix. If not used with @has_library, the view function
    must have a library_short_name argument.
    """

    def decorator(f):
        # This sets up routes for both the subdomain and the url path prefix.
        # The order of these determines which one will be used by url_for -
        # in this case it's the prefix route.
        # We may want to have a configuration option to specify whether to
        # use a subdomain or a url path prefix.
        prefix_route = app.route("/<library_short_name>" + path, *args, **kwargs)(f)
        subdomain_route = app.route(
            path, subdomain="<library_short_name>", *args, **kwargs
        )(prefix_route)
        default_library_route = app.route(path, *args, **kwargs)(subdomain_route)
        return default_library_route

    return decorator

def library_dir_route(path, *args, **kwargs):
    """Decorator to create library routes that work with or without a
    trailing slash."""
    if path.endswith("/"):
        path_without_slash = path[:-1]
    else:
        path_without_slash = path

    def decorator(f):
        # By default, creating a route with a slash will make flask redirect
        # requests without the slash, even if that route also exists.
        # Setting strict_slashes to False disables this behavior.
        # This is important for CORS because the redirects are not processed
        # by the CORS decorator and won't be valid CORS responses.

        # Decorate f with four routes, with and without the slash, with a prefix or subdomain
        prefix_slash = app.route(
            "/<library_short_name>" + path_without_slash + "/",
            strict_slashes=False,
            *args,
            **kwargs,
        )(f)
        prefix_no_slash = app.route(
            "/<library_short_name>" + path_without_slash, *args, **kwargs
        )(prefix_slash)
        subdomain_slash = app.route(
            path_without_slash + "/",
            strict_slashes=False,
            subdomain="<library_short_name>",
            *args,
            **kwargs,
        )(prefix_no_slash)
        subdomain_no_slash = app.route(
            path_without_slash, subdomain="<library_short_name>", *args, **kwargs
        )(subdomain_slash)
        default_library_slash = app.route(path_without_slash, *args, **kwargs)(
            subdomain_no_slash
        )
        default_library_no_slash = app.route(path_without_slash + "/", *args, **kwargs)(
            default_library_slash
        )
        return default_library_no_slash

    return decorator

@library_route("/", strict_slashes=False)
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def index():
    return app.manager.index_controller()

@library_route("/authentication_document")
@has_library
@returns_problem_detail
@compressible
def authentication_document():
    return app.manager.index_controller.authentication_document()

@library_dir_route("/groups", defaults=dict(lane_identifier=None))
@library_route("/groups/<lane_identifier>")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def acquisition_groups(lane_identifier):
    return app.manager.opds_feeds.groups(lane_identifier)

@library_route("/feed/qa/series")
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
@compressible
def qa_series_feed():
    return app.manager.opds_feeds.qa_series_feed()

@library_route("/feed/qa")
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
@compressible
def qa_feed():
    return app.manager.opds_feeds.qa_feed()

@library_dir_route("/feed", defaults=dict(lane_identifier=None))
@library_route("/feed/<lane_identifier>")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def feed(lane_identifier):
    return app.manager.opds_feeds.feed(lane_identifier)

@library_dir_route("/navigation", defaults=dict(lane_identifier=None))
@library_route("/navigation/<lane_identifier>")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def navigation_feed(lane_identifier):
    return app.manager.opds_feeds.navigation(lane_identifier)

@library_route("/crawlable")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def crawlable_library_feed():
    return app.manager.opds_feeds.crawlable_library_feed()

@library_route("/lists/<list_name>/crawlable")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def crawlable_list_feed(list_name):
    return app.manager.opds_feeds.crawlable_list_feed(list_name)

@app.route("/collections/<collection_name>/crawlable")
@allows_patron_web
@returns_problem_detail
@compressible
def crawlable_collection_feed(collection_name):
    return app.manager.opds_feeds.crawlable_collection_feed(collection_name)

@library_route("/opds2/publications")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def opds2_publications():
    return app.manager.opds2_feeds.publications()

@library_route("/opds2/navigation")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def opds2_navigation():
    return app.manager.opds2_feeds.navigation()

@library_route("/marc")
@has_library
@returns_problem_detail
@compressible
def marc_page():
    return app.manager.marc_records.download_page()

@library_dir_route("/search", defaults=dict(lane_identifier=None))
@library_route("/search/<lane_identifier>")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def lane_search(lane_identifier):
    return app.manager.opds_feeds.search(lane_identifier)

@library_dir_route("/patrons/me", methods=["GET", "PUT"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def patron_profile():
    return app.manager.profiles.protocol()

@library_dir_route("/patrons/me/devices", methods=["GET"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def patron_devices():
    return app.manager.patron_devices.get_patron_device()

@library_dir_route("/patrons/me/devices", methods=["PUT"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def put_patron_devices():
    return app.manager.patron_devices.create_patron_device()

@library_dir_route("/patrons/me/devices", methods=["DELETE"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def delete_patron_devices():
    return app.manager.patron_devices.delete_patron_device()

@library_dir_route("/patrons/me/token", methods=["POST"])
@api_spec.validate(resp=SpecResponse(HTTP_200=PatronAuthAccessToken), tags=["patron"])
@has_library
@requires_auth
@returns_problem_detail
def patron_auth_token():
    return app.manager.patron_auth_token.get_token()

@library_dir_route("/loans", methods=["GET", "HEAD"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
@compressible
def active_loans():
    return app.manager.loans.sync()

@library_route("/annotations/", methods=["HEAD", "GET", "POST"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
@compressible
def annotations():
    return app.manager.annotations.container()

@library_route("/annotations/<annotation_id>", methods=["HEAD", "GET", "DELETE"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
@compressible
def annotation_detail(annotation_id):
    return app.manager.annotations.detail(annotation_id)

@library_route("/annotations/<identifier_type>/<path:identifier>", methods=["GET"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
@compressible
def annotations_for_work(identifier_type, identifier):
    return app.manager.annotations.container_for_work(identifier_type, identifier)

@library_route(
    "/works/<identifier_type>/<path:identifier>/borrow", methods=["GET", "PUT"]
)
@library_route(
    "/works/<identifier_type>/<path:identifier>/borrow/<mechanism_id>",
    methods=["GET", "PUT"],
)
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def borrow(identifier_type, identifier, mechanism_id=None):
    return app.manager.loans.borrow(identifier_type, identifier, mechanism_id)

@library_route("/works/<license_pool_id>/fulfill")
@library_route("/works/<license_pool_id>/fulfill/<mechanism_id>")
@library_route("/works/<license_pool_id>/fulfill/<mechanism_id>/<part>")
@has_library
@allows_patron_web
@returns_problem_detail
def fulfill(license_pool_id, mechanism_id=None, part=None):
    return app.manager.loans.fulfill(license_pool_id, mechanism_id, part)

@library_route("/loans/<license_pool_id>/revoke", methods=["GET", "PUT"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def revoke_loan_or_hold(license_pool_id):
    return app.manager.loans.revoke(license_pool_id)

@library_route("/loans/<identifier_type>/<path:identifier>", methods=["GET", "DELETE"])
@has_library
@allows_patron_web
@requires_auth
@returns_problem_detail
def loan_or_hold_detail(identifier_type, identifier):
    return app.manager.loans.detail(identifier_type, identifier)

@library_dir_route("/works")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def work():
    return app.manager.urn_lookup.work_lookup("work")

@library_dir_route(
    "/works/contributor/<contributor_name>",
    defaults=dict(languages=None, audiences=None),
)
@library_dir_route(
    "/works/contributor/<contributor_name>/<languages>", defaults=dict(audiences=None)
)
@library_route("/works/contributor/<contributor_name>/<languages>/<audiences>")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def contributor(contributor_name, languages, audiences):
    return app.manager.work_controller.contributor(
        contributor_name, languages, audiences
    )

@library_dir_route(
    "/works/series/<series_name>", defaults=dict(languages=None, audiences=None)
)
@library_dir_route(
    "/works/series/<series_name>/<languages>", defaults=dict(audiences=None)
)
@library_route("/works/series/<series_name>/<languages>/<audiences>")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def series(series_name, languages, audiences):
    return app.manager.work_controller.series(series_name, languages, audiences)

@library_route("/works/<identifier_type>/<path:identifier>")
@has_library
@allows_auth
@allows_patron_web
@returns_problem_detail
@compressible
def permalink(identifier_type, identifier):
    return app.manager.work_controller.permalink(identifier_type, identifier)

@library_route("/works/<identifier_type>/<path:identifier>/recommendations")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def recommendations(identifier_type, identifier):
    return app.manager.work_controller.recommendations(identifier_type, identifier)

@library_route("/works/<identifier_type>/<path:identifier>/related_books")
@has_library
@allows_patron_web
@returns_problem_detail
@compressible
def related_books(identifier_type, identifier):
    return app.manager.work_controller.related(identifier_type, identifier)

@library_route("/analytics/<identifier_type>/<path:identifier>/<event_type>")
@has_library
@allows_auth
@allows_patron_web
@returns_problem_detail
def track_analytics_event(identifier_type, identifier, event_type):
    return app.manager.analytics_controller.track_event(
        identifier_type, identifier, event_type
    )

@library_route(
    "/playtimes/<int:collection_id>/<identifier_type>/<path:identifier>",
    methods=["POST"],
)
@has_library
@requires_auth
@api_spec.validate(
    resp=SpecResponse(HTTP_200=PlaytimeEntriesPostResponse),
    body=PlaytimeEntriesPost,
    tags=["analytics"],
)
@returns_problem_detail
def track_playtime_events(collection_id, identifier_type, identifier):
    """The actual response type is 207, but due to a bug in flask-pydantic-spec we must document it as a 200"""
    return app.manager.playtime_entries.track_playtimes(
        collection_id, identifier_type, identifier
    )

# Route that redirects to the authentication URL for a SAML provider
@library_route("/saml_authenticate")
@has_library
@returns_problem_detail
def saml_authenticate():
    return app.manager.saml_controller.saml_authentication_redirect(
        flask.request.args, app.manager._db
    )

# Redirect URI for SAML providers
# NOTE: we cannot use @has_library decorator and append a library's name to saml_calback route
# (e.g. https://cm.org/LIBRARY_NAME/saml_callback).
# The URL of the SP's assertion consumer service (saml_callback) should be constant:
# SP's metadata is registered in the IdP and cannot change.
# If we try to append a library's name to the ACS's URL sent as a part of the SAML request,
# the IdP will fail this request because the URL mentioned in the request and
# the URL saved in the SP's metadata configured in this IdP will differ.
# Library's name is passed as a part of the relay state and processed in SAMLController.saml_authentication_callback
@returns_problem_detail
@app.route("/saml_callback", methods=["POST"])
def saml_callback():
    return app.manager.saml_controller.saml_authentication_callback(
        request, app.manager._db
    )

# Loan notifications for ODL distributors, eg. Feedbooks
@library_route("/odl_notify/<loan_id>", methods=["GET", "POST"])
@has_library
@returns_problem_detail
def odl_notify(loan_id):
    return app.manager.odl_notification_controller.notify(loan_id)

# Controllers used for operations purposes
@app.route("/version.json")
def application_version():
    return app.manager.version.version()

# TODO: This route is deprecated and should be removed in a
#       future release of the code, it has been left here for
#       one release to ease any deployment issues.
@app.route("/heartbeat")
def heartbeat():
    version_info = application_version()
    version_info[
        "WARNING"
    ] = "The /heartbeat endpoint is deprecated. Please use /version.json instead."
    return version_info

@app.route("/healthcheck.html")
def health_check():
    return Response("", 200)

@app.route("/images/<filename>")
def static_image(filename):
    return app.manager.static_files.image(filename)


import pytest
from src.jchess.gameplay import *
from src.jchess.constants import Piece, Color

def test_board_creation():
    board = Board()
    assert len(board.grid) == 8
    assert len(board.grid[0]) == 8

@pytest.mark.parametrize("position,piece", [('e8', Piece.BLACK_KING),
                                            ('a1', Piece.WHITE_ROOK),
                                            ('a2', Piece.WHITE_PAWN),
                                            ('g1', Piece.WHITE_KNIGHT),
                                            ('f8', Piece.BLACK_BISHOP),
                                            ('h2', Piece.WHITE_PAWN),  # Harry
                                            ('e4', Piece.EMPTY)
                                            ])
def test_initial_position(position, piece):
    board = Board()
    assert board.get_piece_at_position(position) == piece

def test_making_moves():
    board = Board()
    board.make_move("Nf3")
    piece_at_f3 = board.get_piece_at_position("f3")
    assert piece_at_f3 == Piece.WHITE_KNIGHT
    assert board.turn == Color.BLACK

@pytest.mark.parametrize("notation,fromto", [("Nf3", "g1f3"), ("e4", "e2e4"), ("h3", "h2h3")])
def test_notation_to_fromto(notation, fromto):
    board = Board()
    assert board.notation_to_fromto(notation) == fromto

def test_notation_to_fromto_difficult():
    board = Board()
    board.make_move("e3")
    fromto = board.notation_to_fromto("e4")
    assert fromto == "e3e4"

def test_illegal_move():
    board = Board()
    with pytest.raises(IllegalMove):
        board.make_move('0-0')


from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

from photo_project import (
    PhotoProject,
    Photo,
    PhotoPerson,
    Person,
    PersonRecognizer,
    PersonRecognizerCombined,
    Person2Video,
    Person2VideoMode,
    PhotoQueryParameters
)

from util_functions import resize_image,force_image_size

import cv2
import random
import logging
from datetime import date

class PhotosMainWindow:
    pass

class QueryDialog(QDialog):

    def __init__(self, main_window:PhotosMainWindow ):
        super(QueryDialog, self).__init__(main_window)
        self.main_window = main_window
        self.query_parameters = PhotoQueryParameters()
        self.setWindowTitle('Custom query')

        self.layout = QGridLayout(self)

        self.person_list = QComboBox()
        self.person_list.addItem('')
        for person in Person.select():
                self.person_list.addItem(person.name)
        self.layout.addWidget(QLabel('Person'),1,0)
        self.layout.addWidget(self.person_list,1,2)

        self.assigned_by_list = QComboBox()
        # TODO: get values dynamic from from database
        self.assigned_by_list.addItems(['','manual','FaceDetect','PersonRecognize'])
        self.layout.addWidget(QLabel('Assigned_by'),2,0)
        self.layout.addWidget(self.assigned_by_list,2,2)

        self.path           = QLineEdit()
        self.path_operator  = QComboBox()
        self.path_operator.addItems(PhotoQueryParameters.STR_OPERATORS)
        self.layout.addWidget(QLabel('Path'),3,0)
        self.layout.addWidget(self.path_operator,3,1)
        self.layout.addWidget(self.path,3,2)

        self.nr_of_faces = QSpinBox(self)
        self.nr_of_faces.setRange(0,20)
        self.nr_of_faces.setSpecialValueText('')
        self.nr_of_faces_operator  = QComboBox()
        self.nr_of_faces_operator.addItems(PhotoQueryParameters.NR_OPERATORS)
        self.layout.addWidget(QLabel('Nr of faces'),4,0)
        self.layout.addWidget(self.nr_of_faces_operator,4,1)
        self.layout.addWidget(self.nr_of_faces,4,2)

        self.nr_of_persons = QSpinBox(self)
        self.nr_of_persons.setRange(0,20)
        self.nr_of_persons.setSpecialValueText('')
        self.nr_of_persons_operator  = QComboBox()
        self.nr_of_persons_operator.addItems(PhotoQueryParameters.NR_OPERATORS)
        self.layout.addWidget(QLabel('Nr of persons'),5,0)
        self.layout.addWidget(self.nr_of_persons_operator,5,1)
        self.layout.addWidget(self.nr_of_persons,5,2)

        self.exists_map = {
            '':                 None,
            'Exists':           True,
            'Does not exists':  False
        }
        self.is_existing = QComboBox()
        self.is_existing.addItems(self.exists_map.keys())
        self.layout.addWidget(QLabel('File status'),6,0)
        self.layout.addWidget(self.is_existing,6,2)

        self.duplicate_map = {
            '':                         None,
            'Has duplicates':           True,
            'Does not have duplicates': False
        }
        self.duplicate = QComboBox()
        self.duplicate.addItems(self.duplicate_map.keys())
        self.layout.addWidget(QLabel('Duplicates'),7,0)
        self.layout.addWidget(self.duplicate,7,2)

        self.min_date= QDate.fromString( "01/01/2000", "dd/MM/yyyy" )
        self.max_date = date.today()
        # TODO: get min and max value from database
        self.after = QDateEdit(self)
        self.after.setCalendarPopup(True)
        self.after.setDate(self.min_date)
        self.after.setMinimumDate(self.min_date)
        self.after.setMaximumDate(self.max_date)
        self.layout.addWidget(QLabel('After'),10,0)
        self.layout.addWidget(self.after,10,2)

        self.before = QDateEdit(self)
        self.before.setCalendarPopup(True)
        self.before.setDate(self.max_date)
        self.before.setMinimumDate(self.min_date)
        self.before.setMaximumDate(self.max_date)
        self.layout.addWidget(QLabel('Before'),11,0)
        self.layout.addWidget(self.before,11,2)

        buttons = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            Qt.Horizontal, self)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        self.layout.addWidget(buttons)

    def update_query_parameters(self):
        self.query_parameters.person_name   = self.person_list.currentText()
        self.query_parameters.assigned_by   = self.assigned_by_list.currentText()
        self.query_parameters.path          = self.path.text()
        self.query_parameters.path_operator = self.path_operator.currentText()

        self.query_parameters.nr_of_faces               = self.nr_of_faces.value()
        self.query_parameters.nr_of_faces_operator      = self.nr_of_faces_operator.currentText()
        self.query_parameters.nr_of_persons             = self.nr_of_persons.value()
        self.query_parameters.nr_of_persons_operator    = self.nr_of_persons_operator.currentText()

        self.query_parameters.after         = self.after.dateTime().toPyDateTime()
        self.query_parameters.before        = self.before.dateTime().toPyDateTime()

        self.query_parameters.is_existing   = self.exists_map[self.is_existing.currentText()]
        self.query_parameters.has_duplicate = self.duplicate_map[self.duplicate.currentText()]

    @staticmethod
    def get_query_parameters(main_window:PhotosMainWindow):
        dialog = QueryDialog(main_window)
        result = dialog.exec_()
        dialog.update_query_parameters()
        return (dialog.query_parameters, result == QDialog.Accepted)

class PhotosMainWindow(QMainWindow):

    def __init__(self):
        super(QMainWindow, self).__init__(None)
        self.person_image_width = 200
        self._init_file_menu()
        self._init_tools_menu()
        self._init_query_menu()
        self._init_help_menu()
        self._init_photo_layout()
        self.current_photo:Photo = None
        self.current_photos: list[Photo] = []
        self.current_index:int = -1

    def _init_file_menu(self):
        self.file_menu = self.menuBar().addMenu("&File")

        self.open_action = QAction("&Open")
        self.open_action.triggered.connect(self.open_file)
        self.open_action.setShortcut(QKeySequence.StandardKey.Open)

        self.close_action = QAction("&Close File")
        self.close_action.triggered.connect(self.close_file)
        self.close_action.setEnabled(False)

        self.exit_action = QAction("&Exit")
        self.exit_action.triggered.connect(self.exit)

        self.file_menu.addAction(self.open_action)
        self.file_menu.addAction(self.close_action)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.exit_action)

    def _init_tools_menu(self):
        self.tools_menu = self.menuBar().addMenu("&Tools")
        self.train_action = QAction("&Run Training face model")
        self.train_action.triggered.connect(self.train_face_model)
        self.person_video_action = QAction("&Make person video")
        self.person_video_action.triggered.connect(self.make_person_video)

        self.tools_menu.addAction(self.train_action)
        self.tools_menu.addSeparator()
        self.tools_menu.addAction(self.person_video_action)

    def _init_query_menu(self):
        self.query_menu = self.menuBar().addMenu("&Query")

        self.query_group_act = QActionGroup(self.query_menu)
        texts = ["All", "Persons", "Recognized Persons","Custom","Duplicates"]
        functions = [self.query_all,self.query_persons,self.query_recognized,self.query_custom,self.query_duplicates]
        for text, function in zip(texts,functions):
            action = QAction(text, self.query_menu, checkable=True, checked=text==texts[0])
            self.query_menu.addAction(action)
            action.query = function
            self.query_group_act.addAction(action)
        self.query_group_act.setExclusive(True)
        self.query_group_act.triggered.connect(self.update_query)
        self.query_group_act.setEnabled(False)

    def _init_help_menu(self):
        self.help_menu = self.menuBar().addMenu("&Help")
        self.about_action = QAction("&About")
        self.help_menu.addAction(self.about_action)

        self.help_menu.addSeparator()
        self.logging_menu = self.help_menu.addMenu("&Logging")
        self.logging_group_act = QActionGroup(self.logging_menu)
        texts = ["CRITICAL", "ERROR", "WARN" ,"INFO","DEBUG"]
        levels = [logging.CRITICAL,logging.ERROR,logging.WARN,logging.INFO,logging.DEBUG]
        for text, level in zip(texts,levels):
            action = QAction(text, self.help_menu, checkable=True, checked=text==texts[0])
            self.logging_menu.addAction(action)
            action.level = level
            self.logging_group_act.addAction(action)
        self.logging_group_act.setExclusive(True)
        self.logging_group_act.triggered.connect(self.reset_logging)

        self.about_action.triggered.connect(self.show_about_dialog)

    def _init_photo_layout(self):
        self.image_frame = QLabel()
        self.info = QLabel()
        self.info.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
        self.info.setMaximumHeight(20)
        self.prev_button = QPushButton('Previous')
        self.prev_button.clicked.connect(self.show_previous_photo)
        self.random_button = QPushButton('Random')
        self.random_button.clicked.connect(self.show_random_photo)
        self.next_button = QPushButton('Next')
        self.next_button.clicked.connect(self.show_next_photo)

        self.first_button = QPushButton('First')
        self.first_button.clicked.connect(self.show_first_photo)
        self.last_button = QPushButton('Last')
        self.last_button.clicked.connect(self.show_last_photo)

        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        self.main_wid = QWidget(self)
        self.setCentralWidget(self.main_wid)

        self.main_layout = QHBoxLayout()
        self.left_layout = QVBoxLayout()
        self.right_layout = QGridLayout()
        self.im_layout = QVBoxLayout()
        self.navigation_layout = QHBoxLayout()

        self.person_image_frame = QLabel()
        self.person_image_frame.setFixedWidth(self.person_image_width)
        self.person_image_frame.setFixedHeight(self.person_image_width)

        person_name_label = QLabel('Person name:')
        person_assigned_by_label = QLabel('Assigned by:')
        self.person_assigned_by = QLabel('')

        self.photo_type = QComboBox()
        self.photo_type.addItems(['Normal','Gray','Normalized'])
        self.photo_type.currentTextChanged.connect(self.photo_type_changed)

        self.person_list = QComboBox()
        self.person_list.addItems(['Unknown','Not a person','New person'])

        self.person_name = QLineEdit()
        person_name_label.setFixedWidth(200)
        person_assigned_by_label.setFixedWidth(200)
        self.person_assigned_by.setFixedWidth(200)

        person_predicted_label = QLabel('Predicted person:')
        self.person_predicted = QLabel('')

        person_confidence_label = QLabel('Confidence prediction:')
        self.person_confidence = QLabel('')

        self.person_name.setFixedWidth(200)
        self.person_name.setMaxLength(255)

        self.save_button = QPushButton('Save')
        self.save_button.clicked.connect(self.save_person)

        self.prev_person_button = QPushButton('Previous')
        self.prev_person_button.clicked.connect(self.previous_person)
        self.next_person_button = QPushButton('Next')
        self.next_person_button.clicked.connect(self.next_person)

        self.right_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.right_layout.addWidget(person_assigned_by_label,0,0)
        self.right_layout.addWidget(self.person_assigned_by,1,0)
        self.right_layout.addWidget(self.person_image_frame,2,0)
        self.right_layout.addWidget(self.photo_type,3,0)
        self.right_layout.addWidget(person_name_label,4,0)
        self.right_layout.addWidget(self.person_list,5,0)
        self.right_layout.addWidget(self.person_name,6,0 )
        self.right_layout.addWidget(self.save_button,7,0 )
        self.right_layout.addWidget(self.next_person_button,8,0 )
        self.right_layout.addWidget(self.prev_person_button,9,0 )

        self.right_layout.addWidget(person_predicted_label,20,0 )
        self.right_layout.addWidget(self.person_predicted,21,0 )

        self.right_layout.addWidget(person_confidence_label,22,0 )
        self.right_layout.addWidget(self.person_confidence,23,0 )

        self.navigation_layout.addWidget(self.first_button)
        self.navigation_layout.addWidget(self.prev_button)
        self.navigation_layout.addWidget(self.random_button)
        self.navigation_layout.addWidget(self.next_button)
        self.navigation_layout.addWidget(self.last_button)

        self.im_layout.addWidget(self.image_frame)

        self.left_layout.addWidget(self.info)
        self.left_layout.addLayout(self.navigation_layout)
        self.left_layout.addLayout(self.im_layout)
        self.main_layout.addLayout(self.left_layout)
        self.main_layout.addLayout(self.right_layout)

        self.main_wid.setLayout(self.main_layout)

    def update_query(self, action:QAction):
        action.query()
        self.current_index = -1
        if len(self.current_photos) > 0:
            self.show_first_photo()
        else:
            self.clear_photo()

    def query_all(self):
        self.current_photos: list[Photo] = [ photo for photo in Photo.select().order_by(Photo.photo_id)]

    def query_persons(self):
        self.current_photos: list[Photo] = [ photo for photo in PhotoProject.get_persons()]

    def query_duplicates(self):
        self.current_photos: list[Photo] = [ photo for photo in PhotoProject.get_duplicates_as_objects()]

    def query_recognized(self):
        self.current_photos: list[Photo] = [ photo for photo in PhotoProject.get_recognized()]

    def query_custom(self):
        query_parameters, result = QueryDialog.get_query_parameters(self)
        if result:
            self.current_photos: list[Photo] = [ photo for photo in PhotoProject.get_custom(query_parameters)]

    def show_status_query(self):
        nrFound = len(self.current_photos)

        if nrFound > 0:
            self.status_bar.showMessage(f'Found {nrFound} photos. Now showing photo {self.current_index + 1}')
        else:
            self.status_bar.showMessage(f'Found {nrFound} photos.')

    def train_face_model(self):
        self.recognizer.run_training_all()

    def make_person_video(self):
        person_name = self.person_list.currentText()
        if person_name == 'Unknown':
            return
        if person_name == 'Not a person':
            return
        if person_name == 'New person':
            person_name = self.person_name.text().strip()
            if len(person_name) == 0:
                return

        person = Person.get(name=person_name)
        person2video = Person2Video(person=person)
        person2video.load_images()

        filename = QFileDialog.getSaveFileName(self, "Save",filter='*.avi')[0]
        if filename:
            if not filename.endswith('.avi'):
                filename += '.avi'

            output_filename = filename.replace('.avi','_face.avi')
            person2video.write(filename=output_filename,mode = Person2VideoMode.FACE)
            output_filename = filename.replace('.avi','_full.avi')
            person2video.write(filename=output_filename,mode=Person2VideoMode.FULL)

    def predict_person(self):
        if len(self.current_photo.persons) == 0 or not self.recognizer.is_trained:
            self.person_predicted.setText('')
            self.person_confidence.setText('')
            return
        photo_person:PhotoPerson = self.current_photo.persons[self.current_photo_person_index]

        person, confidence = self.recognizer.predict(photo_person)
        self.person_predicted.setText(person.name)
        self.person_confidence.setText(f'{confidence:0.3f} %')

    def save_person(self):
        if len(self.current_photo.persons) == 0:
            return

        photo_person:PhotoPerson = self.current_photo.persons[self.current_photo_person_index]
        person_name = self.person_list.currentText()
        if person_name == 'Unknown':
            pass
        if person_name == 'Not a person':
            pass
        if person_name == 'New person':
            person_name = self.person_name.text().strip()
            if len(person_name) == 0:
                return
        person, created = Person.get_or_create(name=person_name)
        if created:
            print(f'Created person: {person_name}')
            self.person_list.addItem(person_name)
        photo_person.person = person
        photo_person.assigned_by = 'manual'
        photo_person.save()

        self.person_assigned_by.setText(photo_person.assigned_by)

    def open_file(self):
        path = QFileDialog.getOpenFileName(self, "Open",filter='*.db')[0]
        if path:
            PhotoProject.set_current_database(path)
            self.close_action.setEnabled(True)
            self.open_action.setEnabled(False)
            self.query_group_act.setEnabled(True)

            for person in Person.select():
                self.person_list.addItem(person.name)

            self.recognizer = PersonRecognizer(recognizer_type='LBPH')
            #self.recognizer = PersonRecognizerCombined()
            self.query_all()
            self.show_random_photo()

    def close_file(self):
        PhotoProject.close_current_database()
        self.close_action.setEnabled(False)
        self.open_action.setEnabled(True)
        self.query_group_act.setEnabled(False)
        self.clear_photo()

    def exit(self):
        if self.close_action.isEnabled():
            self.close_file()
        self.close()

    def show_next_photo(self):
        if len(self.current_photos) == 0:
            return
        self.set_current_photo_by_index(self.current_index + 1)
        self.show_photo()

    def show_first_photo(self):
        if len(self.current_photos) == 0:
            return
        self.set_current_photo_by_index(0)
        self.show_photo()

    def show_last_photo(self):
        if len(self.current_photos) == 0:
            return
        self.set_current_photo_by_index(len(self.current_photos) -1)
        self.show_photo()

    def show_random_photo(self):
        if len(self.current_photos) == 0:
            return
        index = random.randint(0,len(self.current_photos))
        self.set_current_photo_by_index(index)
        self.show_photo()

    def show_previous_photo(self):
        if len(self.current_photos) == 0:
            return
        self.set_current_photo_by_index(self.current_index - 1)
        self.show_photo()

    def set_current_photo_by_index(self,index:int):
        if index < 0 or index > (len(self.current_photos) -1):
            return
        self.current_index = index
        self.current_photo = self.current_photos[self.current_index]

    def clear_photo(self):
        self.image_frame.clear()
        self.info.setText('')
        self.show_status_query()
        self.clear_person()

    def show_photo(self):
        try:
            self.image_cv2 = cv2.imread(self.current_photo.full_path)
            for person in self.current_photo.persons:
                cv2.rectangle(self.image_cv2,(person.x,person.y),(person.x+person.w,person.y+person.h),(255,0,0),4)

            self.current_photo_person_index = 0
            self.display_person()

            frame_width = self.image_frame.size().width()
            frame_height = self.image_frame.size().height()
            #self.image_cv2_resized = resize_image(self.image_cv2,max_height=self.image_frame.size().height())
            self.image_cv2_resized = force_image_size(self.image_cv2,width=frame_width ,height=frame_height)

            logging.info(f"Resized for GUI from {self.image_cv2.shape} to {self.image_cv2_resized.shape} to fit frame w,h {frame_width},{frame_height}  image: {self.current_photo.full_path}")

            self.image = QImage(
                self.image_cv2_resized.data,
                self.image_cv2_resized.shape[1],
                self.image_cv2_resized.shape[0],
                self.image_cv2_resized.strides[0],
                QImage.Format_RGB888
            ).rgbSwapped()
            self.image_frame.setPixmap(QPixmap.fromImage(self.image))
            #self.image_frame.setScaledContents( True )
            self.image_frame.setSizePolicy( QSizePolicy.Ignored, QSizePolicy.Ignored )
            self.info.setText(f"{self.current_photo.full_path}\t timestamp: {self.current_photo.timestamp}" )
            self.show_status_query()
        except Exception as err:
            logging.exception(err)
            err_box = QMessageBox()
            err_box.setIcon(QMessageBox.Warning)
            err_box.setText('A error occurred in photo display')
            err_box.setDetailedText(str(err))
            err_box.setStandardButtons(QMessageBox.Ok)
            err_box.exec_()

    def next_person(self):
        if self.set_current_person_by_index(self.current_photo_person_index + 1):
            self.display_person()

    def previous_person(self):
        if self.set_current_person_by_index(self.current_photo_person_index - 1):
            self.display_person()

    def set_current_person_by_index(self,index:int):
        if index < 0 or index > (len(self.current_photo.persons) -1):
            return False
        self.current_photo_person_index = index
        return True

    def display_person(self):
        self.show_person_details()
        self.predict_person()

    def clear_person(self):
        self.person_image_frame.clear()
        name = 'Unknown'
        index = self.person_list.findText(name)
        self.person_list.setCurrentIndex(index)
        self.person_predicted.setText('')
        self.person_confidence.setText('')

    def show_person_details(self):
        self.person_name.setText('')
        if len(self.current_photo.persons) == 0:
            self.clear_person()
            return

        self.set_person_image()

        self.person_assigned_by.setText(self.current_photo.persons[self.current_photo_person_index].assigned_by)
        person:Person = self.current_photo.persons[self.current_photo_person_index].person
        if person is not None:
            name = person.name
        else:
            name = 'Unknown'
        index = self.person_list.findText(name)
        self.person_list.setCurrentIndex(index)

    def photo_type_changed(self,value):
        self.set_person_image()

    def set_person_image(self):
        if len(self.current_photo.persons) == 0:
            return None

        photo_person:PhotoPerson = self.current_photo.persons[self.current_photo_person_index]

        display_type = self.photo_type.currentText()
        if display_type == 'Normalized':
            person_img_cv2 = self.recognizer.get_person_normalized_image(photo_person=photo_person)
            self.person_image = QImage(
                person_img_cv2.data,
                person_img_cv2.shape[1],
                person_img_cv2.shape[0],
                person_img_cv2.shape[1],
                QImage.Format_Grayscale8
            )
        elif display_type == 'Gray':
            person_img_cv2 = resize_image(self.image_cv2[photo_person.y:photo_person.y + photo_person.h, photo_person.x:photo_person.x + photo_person.w],max_width=self.person_image_width)
            person_img_cv2 = cv2.cvtColor(person_img_cv2, cv2.COLOR_BGR2GRAY)
            self.person_image = QImage(
                person_img_cv2.data,
                person_img_cv2.shape[1],
                person_img_cv2.shape[0],
                person_img_cv2.shape[1],
                QImage.Format_Grayscale8
            )
        else:
            person_img_cv2 = resize_image(self.image_cv2[photo_person.y:photo_person.y + photo_person.h, photo_person.x:photo_person.x + photo_person.w],max_width=self.person_image_width)

            self.person_image = QImage(
                person_img_cv2.data,
                person_img_cv2.shape[1],
                person_img_cv2.shape[0],
                person_img_cv2.strides[0],
                QImage.Format_RGB888
            ).rgbSwapped()
        self.person_image_frame.setPixmap(QPixmap.fromImage(self.person_image))

    def show_about_dialog(self):
        text = "<center>" \
            "<h1>Foto's</h1>" \
            "" \
            "<img src=icon.svg>" \
            "</center>" \
            "<p>Version 0.0.2<br/>" \
            "Copyright © F. van Bakel.</p>"
        QMessageBox.about(self, "About my app", text)

    def reset_logging(self, action:QAction):

        level = action.level
        for handler in logging.root.handlers[:]:
            logging.root.removeHandler(handler)
        logging.basicConfig(filename='photo_gui.log',
                    filemode='w',
                    format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
                    datefmt='%H:%M:%S',
                    level=level)
        logging.info('Logging reset')

def start_app():

    app = QApplication([])
    app.setApplicationName("fotos")
    app.setOrganizationName("fvbakel")
    app.setApplicationDisplayName("Foto's");
    #settings = QSettings(app.organizationName(), app.applicationName())

    window = PhotosMainWindow()
    window.showMaximized()
    app.exec()

if __name__ == '__main__':
    start_app()


from django.core.management.base import NoArgsCommand
from optparse import make_option
import csv
from wall.models import Wall

class Command(NoArgsCommand):
    option_list = NoArgsCommand.option_list + (
       make_option('--wall_size', dest='wall_size', default="-1",
           help='Override the wall max_items limit with this number.'),
    )
    help = "Trim excess wall items from all walls."

    requires_model_validation = False

    def handle_noargs(self, **options):
        try:
            item_limit = int(options.get('wall_size', "-1"))
        except:
            item_limit = -1
        walls = Wall.objects.all()
        for wall in walls:
            items = wall.wallitem_set.select_related()
            if item_limit < 0:
                 wall_limit = wall.max_items
            else:
                 wall_limit = item_limit
            if len(items) > wall_limit:
                for item in items[wall_limit:]:
                    # print "delete item", item
                    item.delete()


from django.shortcuts import render, get_object_or_404
from .models import SliderImage, Activity, Service, Room, Category

# Create your views here.

def index(request):
    images = SliderImage.objects.all()
    main_image = None
    for image in images:
        if image.tag == 'main':
            main_image = image
            images = SliderImage.objects.exclude(tag='main')
            break

    room_info = {}
    rooms = Room.objects.all()
    for room in rooms[:3]:
        room_info[room.name] = [room, room.images.all()[0]]

    context = {
        "main_image": main_image,
        "secondary_images": images,
        "room_info": room_info
    }
    return render(request, 'quovadisapp/index.html', context)

def about(request):
    services = Service.objects.all()
    if len(services) > 8:
        services = services[:8]

    context = {"services": services}

    return render(request, 'quovadisapp/about.html', context)

def activities(request):
    activities = Activity.objects.all()
    context = {"activities": activities}
    return render(request, 'quovadisapp/activities.html', context)

def rooms(request):
    room_info = {}
    rooms = Room.objects.all()
    for room in rooms:
        room_info[room.name] = [room, room.images.all()]
    context = {"room_info": room_info}
    return render(request, 'quovadisapp/rooms.html', context)

def room_detail(request, room_id):
    room = Room.objects.get(id=room_id)
    room_info = [room, room.images.all()]
    context = {"room_info": room_info}
    return render(request, 'quovadisapp/detail.html', context)

def restaurant(request):
    breakfast = []
    lunch = []
    dinner = []
    categories = Category.objects.all()
    for category in categories:
        if category.name == "Breakfast":
            breakfast = category.menu.all()
        elif category.name == "Lunch":
            lunch = category.menu.all()
        elif category.name == "Dinner":
            dinner = category.menu.all()

    context = {"breakfast": breakfast, "lunch": lunch, "dinner": dinner}
    return render(request, 'quovadisapp/restaurant.html', context)

def contact(request):
    return render(request, 'quovadisapp/contact.html')


from tensorflow_serving.apis import prediction_service_pb2_grpc
from tensorflow_serving.apis.predict_pb2 import PredictRequest
import tensorflow as tf
import numpy as np
import grpc
import argparse
import os
from utils.model import load_image
from utils.model import class_names
from utils.model import model_metadata

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--image", type=str, required=True, help="Path to input image")
    args = vars(ap.parse_args())

    print("Prediction using grpc service...")
    input_name = "conv2d_1_input"
    output_name = "fc"

    img, label = load_image(args["image"])
    img = img.numpy()
    img = np.expand_dims(img, axis=0)
    label = label.numpy()
    print("Input image shape: ", img.shape)
    print("Expected Label: ", label)

    request = PredictRequest()
    request.model_spec.name = "fashion_models"
    request.model_spec.signature_name = "serving_default"
    request.inputs[input_name].CopyFrom(tf.make_tensor_proto(img))

    # send request to server
    channel = grpc.insecure_channel("localhost:8500")
    predict_service = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    response = predict_service.Predict(request, timeout=10.0)

    preds = response.outputs[output_name]
    preds = tf.make_ndarray(preds)
    print(type(preds))
    print(preds)

    res = np.argmax(preds)
    expected_label = class_names[label]
    predicted_label = class_names[res]
    print("Expected Label > ", expected_label)
    print("Predicted Label > ", predicted_label)


#保存成Excel文件的步骤
#1、导入xlwt库
#2、创建book工作簿
#3、创建sheet工作表
#4、写入
#5、保存save
#！！！和csv不同之处在于，csv可以每次累计往文件中写入，比如a+模式
#！！！但是Excel的写入是坐标的写入例如 sheet.write(0,0,'hello')就是在第一格写入
#！！！所以，一般c可以边找寻数据边写入csv文件，但最好把所有采集的数据存在列表中，再利用循环统一遍历坐标写入Excel表格

import requests
from lxml import etree
import time
import xlwt

context=[]
headers={
    'User-Agent':'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)'
}

def goto_url(url):
    wb_code=requests.get(url,headers=headers)
    wb_code.encoding='utf-8'
    html=etree.HTML(wb_code.text)
    models=html.xpath('//div[@class="book-mid-info"]')
    for model in models:
        id=model.xpath('h4/a/text()')[0].strip()
        author=model.xpath('p[1]/a[1]/text()')[0].strip()
        lab1=model.xpath('p[1]/a[2]/text()')[0].strip()
        lab2=model.xpath('p[1]/a[3]/text()')[0].strip()
        lab=lab1+'*'+lab2
        words=model.xpath('p[3]/span/span/text()')[0].strip()
        #注意，即使span下面还有许多标签，但span直接取了所有的文字
        info_list=[id,author,lab,words]
        context.append(info_list)

if __name__=='__main__':
    urls=['https://www.qidian.com/all?page={}'.format(num) for num in range(1,4)]
    for url in urls:
        goto_url(url)
        time.sleep(1)
    #！！！！！！！！！！！！！！！！！
    head=['id','author','lab','words']
    book=xlwt.Workbook(encoding="utf-8")
    sheet=book.add_sheet('sheet1')
    for i in range(len(head)):
        sheet.write(0,i,head[i])
    i=1
    for each in context:
        for j in range(len(each)):
            sheet.write(i,j,each[j])
        i+=1
    book.save('5.4例5：结果.xls')


#!/usr/bin/env python3
"""
@note Text layers are not supported by telegram
"""
import sys
import os
sys.path.insert(0, os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    "lib"
))
from lottie.utils import script
from lottie import objects
from lottie import Color, Point

an = objects.Animation(120)
an.fonts = objects.text.FontList()
an.fonts.append(objects.text.Font("sans", name="sans"))
layer = objects.TextLayer()
an.add_layer(layer)

layer.data.add_keyframe(0, objects.text.TextDocument("Text", 200, Color(1, 0, 0), "sans"))
layer.data.add_keyframe(60, objects.text.TextDocument("Here", 200, Color(0, 1, 0), "sans"))
layer.transform.position.value = Point(30, 200)

script.script_main(an)