import matplotlib.pyplot as plt
import numpy as np
import scipy
import scipy.io

def feature_normalize(samples):
    """
    Feature-normalize samples
    :param samples: samples.
    :return: normalized feature
    """
    sam = np.copy(samples)
    col = sam.shape[1]
    for i in range(col):
        std_mean = np.mean(sam[ : ,i])
        sam[ : ,i] = (sam[ : ,i] - std_mean) / np.std(sam[ : ,i])
    return sam

def get_usv(sample_norm):
    m  = sample_norm.shape[0]
    Cov = (1/m) * np.transpose(sample_norm).dot(sample_norm)
    u, s, v = np.linalg.svd(Cov)
    return u,s

def project_data(samples, U, K):
    """
    Computes the reduced data representation when
    projecting only on to the top "K" eigenvectors
    """
    # Reduced U is the first "K" columns in U
    reduced_U = U[:,0:K]
    res = samples.dot(reduced_U)
    return res

def recover_data(Z, U, K):
    U_inverse = np.linalg.inv(U)
    reduced_U = U_inverse[:K]
    output = np.dot(Z,reduced_U)
    return output

def main():
    datafile = 'data1.mat'
    mat = scipy.io.loadmat(datafile)
    samples = mat['X']

    plt.figure(figsize=(7, 7))
    plt.scatter(samples[:, 0], samples[:, 1], s=30, facecolors='none', edgecolors='b')
    plt.title("Example Dataset", fontsize=18)
    plt.grid(True)
    # Feature normalize

    samples_norm = feature_normalize(samples)

    # Run SVD

    U, S = get_usv(samples_norm)  # IMPLEMENT U.

    # output the top principal component (eigen- vector) found
    # should expect to see an output of about [-0.707 -0.707]"
    print('Top principal component is ', U[:, 0])

    plt.figure(figsize=(7, 7))
    plt.scatter(samples[:, 0], samples[:, 1], s=30, facecolors='none', edgecolors='b')
    plt.title("Example Dataset: PCA Eigenvectors Shown", fontsize=18)
    plt.xlabel('x1', fontsize=18)
    plt.ylabel('x2', fontsize=18)
    plt.grid(True)
    # To draw the principal component, you draw them starting
    # at the mean of the data
    x1_mean = np.mean(samples[ : ,0])
    x2_mean = np.mean(samples[ : ,1])

    arrow1 = plt.arrow(x1_mean,x2_mean,U[:,0][0]*S[0], U[:,0][1]*S[0], color ='r',
                       label = 'First Principle Component')
    arrow2 = plt.arrow(x1_mean,x2_mean,U[:,1][0]*S[1], U[:,1][1]*S[1], color ='g',
                       label = 'Second Principle Component')

    # IMPLEMENT PLOT

    plt.legend(handles = [arrow1, arrow2],loc=4)

    # project the first example onto the first dimension
    # should see a value of about 1.481"

    z = project_data(samples_norm, U, 1)
    print('Projection of the first example is %0.3f.' % float(z[0]))
    recovered_sample = recover_data(z, U, 1)
    print('Recovered approximation of the first example is ', recovered_sample[0])

    plt.figure(figsize=(7, 7))
    plt.scatter(samples_norm[:, 0], samples_norm[:, 1], s=30, facecolors='none',
                edgecolors='b', label='Original Data Points')
    plt.scatter(recovered_sample[:, 0], recovered_sample[:, 1], s=30, facecolors='none',
                edgecolors='r', label='PCA Reduced Data Points')

    plt.title("Example Dataset: Reduced Dimension Points Shown", fontsize=14)
    plt.xlabel('x1 [Feature Normalized]', fontsize=14)
    plt.ylabel('x2 [Feature Normalized]', fontsize=14)
    plt.grid(True)

    for x in range(samples_norm.shape[0]):
        plt.plot([samples_norm[x, 0], recovered_sample[x, 0]], [samples_norm[x, 1], recovered_sample[x, 1]], 'k--')

    plt.legend(loc=4)
    plt.xlim((-2.5, 2.5))
    plt.ylim((-2.5, 2.5))
    plt.show()

if __name__ == '__main__':
    main()


from django.contrib import messages
from django.shortcuts import redirect, render
import bcrypt
from .decorators import login_required
from .models import User, Books, Reviews, Author
from django.db.models import Count
from django.db.utils import IntegrityError

@login_required
def index(request):

    context = {
        'saludo': 'Hola'
    }
    return render(request, 'index.html', context)

@login_required
def books(request):
    usuarios = User.objects.all()
    libros = Books.objects.all()
    resenas = Reviews.objects.all()
    last_three = Reviews.objects.all().order_by('-created_at')[:3]
    autores = Author.objects.all()

    context ={
        'User': usuarios,
        'Books': libros,
        'Reviews': resenas,
        'newest_three_reviews': last_three,
        'Authors':autores,

    }
    return render(request, 'books.html', context)

@login_required
def booksadd(request):
    if request.method == 'GET':
        resenas = Reviews.objects.all()
        libros = Books.objects.all()
        autores = Author.objects.all()
        #estrellas =[]

        context = {
            'books' : libros,
            'Authors': autores,
            'Reviews': resenas,
            #'Estrellas': estrellas,

        }
        return render(request,"booksadd.html", context)

    else:
        errors = Books.objects.basic_validator(request.POST)
        autornuevo = None
        if request.POST['author_id'] == 'other':
            # en este caso hay que crearla
            new_author_name = request.POST['newAuthor']
            old_authors = Author.objects.filter(name__iexact=new_author_name)

            if len(old_authors) > 0:
                messages.error(request,"This author already exists.")
                return redirect("/books/add")
            autornuevo = Author.objects.create(name = new_author_name)

        else:
            # en este caso hay que rescatarla de la DB
            author_id = int(request.POST['author_id'])
            autornuevo = Author.objects.get(id=author_id)

        # Ahora estamos 100% seguros que SI existe 'autornuevo'
        user_id = request.session['user']['id']
        usuario = User.objects.get(id= user_id)
        titulo = request.POST['title']
        resena = request.POST['review']
        score = int(request.POST['stars'])

        '''
        if score == 1:
            estrellas.add('★☆☆☆☆')
        elif rating == 2:
            estrellas.add('★★☆☆☆')
        elif rating == 3:
            estrellas.add('★★★☆☆')
        elif rating == 4:
            estrellas.add('★★★★☆')
        else:
            estrellas.add('★★★★★')
        '''

        if len(errors)> 0: #si el suscriptor lleno mal esta cuestion
            for key, error_message in errors.items():
                messages.error(request, error_message)
            return redirect(f'/books/add')

        old_books = Books.objects.filter(title__iexact=titulo)
        if len(old_books) > 0:
            messages.error(request,"This title already exists.")

        try:
            new_book = Books.objects.create(title = titulo, author= autornuevo)
            new_review = Reviews.objects.create(desc= resena, rating= score, user= usuario, book = new_book)
        except IntegrityError:
            messages.error(request,"This title already exists. You could write another review.")
            return redirect(f'/books/add')

        messages.success(request, 'Book successfully added and Author successfully linked to the book')
    return redirect('/books')

@login_required
def booksview(request, nom):
    if request.method =='GET':
        usuarios = User.objects.all()
        libros = Books.objects.all()
        resenas = Reviews.objects.all()
        autores = Author.objects.all()
        this_book = Books.objects.get(id = int(nom))
        this_book_reviews = this_book.review.all()

        bookids = []
        tot = 0
        for rev in this_book_reviews:
            bookids.append(rev.user_id)
            tot= rev.rating

        if len(bookids) == 0:
            tot_sum = 'No reviews yet'
        else:
            tot_sum = tot/len(bookids)

        #this_review_users = User.objects.filter(review__book= this_book)
        #this_forbidden_to_review_users = User.objects.get(review__book__User__id= you)
        context ={
            'User': usuarios,
            'Books': libros,
            'Reviews': resenas,
            'Author': autores,
            'this_book': this_book,
            'this_book_reviews': this_book_reviews,
            'bookids':bookids,
            'tot_sum': tot_sum,

        }
        return render(request, 'booksview.html', context)
    else:
        #lo que tengo que preguntar ahora es que si existen reseñas de este libro que sean de un user con el mismo id del sesion, entonces no puede agregar
        #una reseña nueva para ese libro. entonces if [user_id for user_id in this_book_reviews.user.id not in request.session.user.id]
        curr_user = request.session['user']
        fresh_resena = request.POST['new_review']
        nuevo_score = int(request.POST['new_stars'])
        new_resena = Reviews.objects.create(desc= fresh_resena, rating= nuevo_score, user= curr_user, book = this_book)
        messages.success(request, 'Review successfully linked to the book')
        return redirect(f'/books/{nom}')

@login_required
def userview(request, nem):
    usuarios = User.objects.all()
    libros = Books.objects.all()
    resenas = Reviews.objects.all()
    autores = Author.objects.all()
    this_user = User.objects.get(id= int(nem))
    this_user_revs = Reviews.objects.filter(user = this_user)
    this_user_total_reviews = []
    for rev in this_user_revs:
        this_user_total_reviews.append(rev)

    context ={
        'User': usuarios,
        'Books': libros,
        'Reviews': resenas,
        'Author': autores,
        'this_user': this_user,
        'this_user_total_reviews': len(this_user_total_reviews)
    }

    return render(request, 'userview.html', context)

@login_required
def deletereview(request, nim):
    this_review = Reviews.objects.get(id = int(nim))
    this_book_id = this_review.book.id
    this_user= this_review.user
    context ={
        'this_user': this_user
    }
    del this_review
    return redirect(f'/books/{this_book_id}')

def addreview(request, nam):
    this_book = Books.objects.get(id = int(nam))
    user_id = request.session['user']['id']
    usuario = User.objects.get(id= user_id)
    fresh_resena = request.POST['new_review']
    nuevo_score = int(request.POST['new_stars'])
    new_resena = Reviews.objects.create(desc= fresh_resena, rating= nuevo_score, user= usuario, book = this_book)
    messages.success(request, 'Review successfully linked to the book')
    return redirect(f'/books/{nam}')

#connected_user_id = request.session['user']['id']
#user_connected = request.session['user']
#got:reviewsonthisbookuser = User.filter.get(this_book__review__user__id= user_id)
#tengo


from flask_restful import Resource, reqparse
import requests
import json
import datetime, time

from execo_g5k import *
import config
from Packages.ResourceAlloc.helper import Helper
from Models import DeploymentModel, g5kHostsModel

parser = reqparse.RequestParser()
parser.add_argument('NbNodes', help = 'This field cannot be blank', required = False)
parser.add_argument('walltime', help = 'This field cannot be blank', required = False)
parser.add_argument('jobName', help = 'This field cannot be blank', required = False)

parser.add_argument('job_id', help = 'This field cannot be blank', required = False)
parser.add_argument('frontend', help = 'This field cannot be blank', required = False)

class NodeReservation(Resource):
    def post(self):
        data = parser.parse_args()
        print(data)
        # try :
        h = Helper()
            #print(h.SiteList())
        hosts,time,job_id,frontend = h.AllocDeployG5k(data['NbNodes'],data['walltime'],data['jobName'])
        #Model Creation
        deployment = DeploymentModel(
            job_name=data['jobName'],
            nb_nodes= data['NbNodes'],
            frontend = frontend,
            walltime= self.toSeconds(data['walltime']),
            job_id = job_id,
            deployment_time =self.toSeconds(str(time))
        )
        try:
            deployment.save_to_db()
            lastDep = int(DeploymentModel.getLastid())-1
            for host in hosts:
                hostBD = g5kHostsModel(
                   id_dep = lastDep,
                   hostName = str(host)
                )
                hostBD.save_to_db()

        except:
            return {'message': 'Something went wrong'}, 500
        return {
            "message" :"successfuly deployed",
            "frontend":str(frontend),
            "job_id" :str(job_id),
            "hosts": hosts,
            "deploymentTime":str(time)
        },200
        # except:
            #return {"message":"error !!"},400
    def toSeconds(self,date):
        Dtime = (datetime.datetime.strptime(date.partition('.')[0], "%H:%M:%S")).time()
        seconds = (Dtime.hour * 60 + Dtime.minute) * 60 + Dtime.second
        return seconds

class NodeDestroy(Resource):
    def post(self):
        data = parser.parse_args()
        print(data)
        h = Helper()
        h.removeResources(data['job_id'],data['frontend'])

class AllDeployments(Resource):
    def get(self):
        print(DeploymentModel.getNextId())
        return DeploymentModel.return_allDeployments()

class getLastJob(Resource):
    def get(self):
        lastDepID = int(DeploymentModel.getLastid())-1
        res,job_id_frontend,status =DeploymentModel.getLastDeployment(lastDepID)
        return res,status

class getJobG5kInfo(Resource):
    def get(self):
        lastDepID = int(DeploymentModel.getLastid())-1
        res,job_id,frontend,status =DeploymentModel.getLastDeployment(lastDepID)
        ansG5k = get_oar_job_info(oar_job_id=job_id, frontend=frontend)
        ansG5k = json.dumps(ansG5k)
        res =json.loads(ansG5k)
        now = datetime.datetime.now()
        unixTS =oar.oar_date_to_unixts(str(now))
        timeLeft = (res['start_date'] + res['walltime'])- unixTS
        print (timeLeft)
        res.update({'time_left': timeLeft})
        res.update({'id_deployment': lastDepID})
        print(res)
        return res


from abc import abstractmethod
from sklearn.metrics.pairwise import haversine_distances

from .ODLHelper import OperatorFunction, OperatorModule
from .ODLHelperCustom import setup_forward_and_backward

from .Graph import *
from .Plotting import *
from .ToyImageGenerator import *
from .PytorchGATHelper import *
from .SNRHelper import add_noise_to_sinograms, find_SNR

import numpy as np
import wandb
import time
from torch_geometric.nn import GATConv, DataParallel
from torch_geometric.loader import DataListLoader
from torch_geometric.data import Data, Dataset
from torch_geometric.utils.random import erdos_renyi_graph
import torch
torch.pi = torch.acos(torch.zeros(1)).item() * 2

import utils.UNetModel as UNetModel
import os.path
################### Loss Enum ##############################
from enum import Enum
class Loss(Enum):
    """ Enum class for different types of losses.
        Sino : l2-distance between clean sinogram and denoised sinogram.
        Fbp: l2.distance between clean input images and fbp(denoised sinogram).
    """
    SINO = 0,
    FBP = 1

################### Denoiser class #########################
class GatBase():
    """ Abstract base class for GatDenoisers and GatValidator
    """
    def __init__(self, args):
        self.consumeArgs(args)
        self.loader = None
        self.unet = None

        if self.VERBOSE:
            self.time_dict = {}

        self.device = torch.device(
            'cuda' if torch.cuda.is_available() else 'cpu')

        self.device0 = self.device = torch.device(
            'cuda:0' if torch.cuda.is_available() else 'cpu')

    ######################## Initializer for child classes ############################
    def consumeArgs(self, args):
        """ Consume all the required arguments

        Args:
            args: The GAT-Denoiser arguments.
        """
        self.args = args
        self.VERBOSE = args.verbose
        self.USE_WANDB = args.use_wandb
        if self.USE_WANDB:
            self.WAND_PROJECT = args.wandb_project

        self.N: int = args.graph_size
        self.K : int = args.k_nn

        self.RESOLUTION: int = args.resolution

        self.GAT_LAYERS: int = args.gat_layers
        self.GAT_HEADS: int = args.gat_heads
        self.GAT_DROPOUT: float = args.gat_dropout
        self.GAT_USE_CONV: bool = args.gat_use_conv
        self.UNET_REFINEMENT: bool = args.unet_refinement
        if self.UNET_REFINEMENT:
            self.UNET_PATH : str = args.unet_path

        if self.GAT_USE_CONV:
            self.GAT_CONV_KERNEL = args.gat_conv_kernel
            self.GAT_CONV_PADDING = args.gat_conv_padding
            self.GAT_CONV_N_LATENT = args.gat_conv_N_latent

    def consumeDenoiserArgs(self, args):
        """ Consume all the required arguments for denoiser instance of GAT-Denoiser
        Args:
            args: The GAT-Denoiser arguments.
        """
        self.M = args.samples
        self.EPOCHS: int = args.epochs

        self.GAT_ADAM_WEIGHTDECAY: float = args.gat_weight_decay
        self.GAT_ADAM_LR: float = args.gat_learning_rate
        self.UNET_TRAIN: bool = args.unet_train

        self.Loss = Loss[args.loss.upper()]

        # check if snr is fixed
        if args.gat_snr_upper is None or args.gat_snr_lower == args.gat_snr_upper:
            if self.VERBOSE:
                print("Using fixed snr!")
            self.SNR = args.gat_snr_lower
            self.FIXED_SNR = True
            self.SNR_LOWER = args.gat_snr_lower
            self.SNR_UPPER = args.gat_snr_upper
        else:
            self.FIXED_SNR = False
            self.SNR_LOWER = args.gat_snr_lower
            self.SNR_UPPER = args.gat_snr_upper

    @classmethod
    def create_validator(cls, args, model_state, run_name=None):
        """ Creates a validator instance.
        Args:
            args: Arguments for setting up the validator.
                The following arguments need to be present:
                    args.verbose : Determines if time is measured or not.
                    args.use_wandb : Determines if wandb is used
                    args.graph_size :  Number of observations in the forward model.
                    args.k_nn: K-NN parameter k for graph size.
                    args.resolution: Observation resolution
                    args.gat_layers: Number of GAT-Denoiser layers (same for convolution and GAT)
                    args.gat_heads: Number of heads
                    args.gat_dropout: GAT dropout
                    args.gat_use_conv: Activate Convolution
                    args.unet_refinement : Activate U-Net reconstruction
                Optional arguments:
                    args.wandb_project: Wandb project name
                    args.gat_conv_kernel : Convolution kernel
                    args.gat_conv_padding: Convolution Padding
                    args.gat_conv_N_latent: Convolution latent channels
                    args.unet_path: Path to U-Net model
        Returns:
            GatValidator: The created instance.
        """
        validator =  GatValidator(args)
        validator.UNET_TRAIN = False
        validator.__initialize_validator__(args, model_state, run_name)
        return validator

    @classmethod
    def create_toyimage_dynamic_denoiser(cls, args):
        """ Create a dynamic toyimage denoiser.
        During training, the denoiser will create uniformly created toy images for every epoch.

        Args:
            args: Arguments for setting up the denoiser.
                The following arguments need to be present:
                    args.verbose : Determines if time is measured or not.
                    args.use_wandb : Determines if wandb is used
                    args.graph_size :  Number of observations in the forward model.
                    args.samples: Number of samples
                    args.k_nn: K-NN parameter k for graph size.
                    args.resolution: Observation resolution

                    args.gat_layers: Number of GAT-Denoiser layers (same for convolution and GAT)
                    args.gat_heads: Number of heads
                    args.gat_dropout: GAT dropout
                    args.gat_use_conv: Activate Convolution
                    args.unet_refinement : Activate U-Net reconstruction
                    args.unet_train: Joint U-Net training
                    self.loss: Loss type

                    args.EPOCHS: Number of training epochs
                    args.gat_weight_decay: Adam optimizer weight decay
                    args.gat_learning_rate: Adam optimizer learning rate
                    args.gat_snr_lower: lower training snr boundary
                    args.gat_snr_upper: upper training snr boundard
                Optional arguments:
                    args.gat_conv_kernel : Convolution kernel
                    args.gat_conv_padding: Convolution Padding
                    args.gat_conv_N_latent: Convolution latent channels
                    args.unet_path: Path to U-Net model
        Returns:
            GatDenoiserToyImagesDynamic: The created instance.
        """
        denoiser = GatDenoiserToyImagesDynamic(args)
        denoiser.__initialize_denoiser__(args)

        return denoiser

    @classmethod
    def create_fixed_images_denoiser(cls, args, model_state = None, optimizer_state = None, run_name=None):
        """ Create a fixed image denoiser. In every epoch, the same training images will be used.

        Args:
            args: Arguments for setting up the denoiser.
            The following arguments need to be present:

        Returns:
            GatDenoiserImagesFixed: The created instance.
        """
        denoiser =  GatDenoiserImagesFixed(args)
        denoiser.__initialize_denoiser__(args, model_state, optimizer_state, run_name)
        return denoiser

    def __initialize_validator__(self, args, model_state, run_name):
        if self.USE_WANDB:
            self.__init_wandb__(self.WAND_PROJECT, args, run_name=run_name)

        self.__execute_and_log_time__(lambda: self.__init_graph_and_forward_backward(args), "init")
        self.__execute_and_log_time__(lambda: self.__prepare_model__(model_state), "prep model")

    def __initialize_denoiser__(self, args, model_state=None, optimizer_state=None, run_name=None):
        self.consumeDenoiserArgs(args)

        if self.USE_WANDB:
            self.__init_wandb__(self.WAND_PROJECT, args, run_name=run_name)

        self.__execute_and_log_time__(lambda: self.__init_graph_and_forward_backward(args), "init")
        self.__execute_and_log_time__(lambda: self.__prepare_model__(model_state), "prep model")
        self.__execute_and_log_time__(lambda: self.__prepare_optimizer__(optimizer_state), "prep optimizer")

    def __init_wandb__(self, wandb_name, args, wandb_user ="cedric-mendelin", run_name = None):
        config = args

        gpus = torch.cuda.device_count()
        config.gpu_count = gpus

        wandb.init(project=wandb_name, entity=wandb_user, config=config, reinit=False)

        if isinstance(self, GatDenoiserImagesFixed) or isinstance(self, GatDenoiserToyImagesDynamic):
            wandb.run.name = f"{wandb_name}-{gpus}-{self.RESOLUTION}-{self.N}-{self.M}-{self.K}-{self.EPOCHS}-{self.GAT_LAYERS}-{self.GAT_HEADS}-{self.GAT_DROPOUT}-{self.GAT_ADAM_WEIGHTDECAY}-{self.SNR_LOWER}-{self.SNR_UPPER}-{self.Loss}-{self.GAT_USE_CONV}-{self.UNET_REFINEMENT}"

        if run_name is not None :
            wandb.run.name = run_name

    ################## Graph - setup during initialization #############
    def __init_graph_and_forward_backward(self, args):
        # parameters for forward and backward operatior
        # Currently radon and filter_back_projection is used.
        self.radon, self.fbp, self.pad = setup_forward_and_backward(self.RESOLUTION, self.N)

        if self.UNET_REFINEMENT:
            checkpoint = torch.load(self.UNET_PATH, map_location=self.device)
            self.unet = UNetModel.UNet(nfilter=128).to(self.device)
            self.unet.load_state_dict(checkpoint['model_state_dict'])
            if self.UNET_TRAIN:
                self.unet.train()
            else:
                self.unet.eval()

        # Parameters for seeting up graph.
        # Currently a circle graph with k neighoburs is used.
        self.graph = self.__prepare_graph__()

    def __prepare_graph__(self, cache=True):
        graph_name = f"graphs/circle_{self.N}_{self.K}.npz"

        if self.K == 0:
            return erdos_renyi_graph(self.N, 0.01)

        # check if graph is cached
        if os.path.isfile(graph_name):
            loaded = np.load(graph_name)
            return torch.tensor(loaded["edges"]).type(torch.long)

        # if not, create graph and cache
        # sample equally spaced points on unit-circle
        angles_np = np.linspace(0, 2 * np.pi, self.N)
        points_np = np.array([np.cos(angles_np), np.sin(angles_np)]).T

        # calculate distances of points on unit-circle
        distances = haversine_distances(points_np, points_np)

        # create the k-nn graph from distances
        _, _, edges = generate_knn_from_distances_with_edges(distances, self.K, ordering='asc', ignoreFirst=True)

        if cache:
            np.savez(graph_name, edges=edges.T)

        return torch.tensor(edges.T).type(torch.long)

    ################### Model - setup during initialization ############
    def __prepare_model__(self, state=None):
        if self.GAT_USE_CONV:
            self.model = GAT(
                in_dim=self.RESOLUTION,
                out_dim=self.RESOLUTION,
                num_layers=self.GAT_LAYERS,
                heads=self.GAT_HEADS,
                dropout=self.GAT_DROPOUT,
                add_conv_before_gat=self.GAT_USE_CONV,
                conv_kernel=self.GAT_CONV_KERNEL,
                conv_padding=self.GAT_CONV_PADDING,
                conv_N_latent=self.GAT_CONV_N_LATENT)
        else:
            self.model = GAT(
                in_dim=self.RESOLUTION,
                out_dim=self.RESOLUTION,
                num_layers=self.GAT_LAYERS,
                heads=self.GAT_HEADS,
                dropout=self.GAT_DROPOUT,
                add_conv_before_gat=False)

        if state != None:
            self.model.load_state_dict(state)

        # model for pytorch.Geometric data parallel
        # this is needed to train on multiple GPUs
        self.model = DataParallel(self.model)
        self.model.to(self.device)

        self.fbp_with_gradient = OperatorModule(self.fbp)

    def __prepare_optimizer__(self, optimizer_state=None):
        # add unet parameters as well when training is activated
        if self.UNET_TRAIN:
            parameters = list(self.model.parameters()) + list(self.unet.parameters())
        else:
            parameters = self.model.parameters()

        self.optimizer = torch.optim.Adam(
            parameters,
            lr=self.GAT_ADAM_LR,
            weight_decay=self.GAT_ADAM_WEIGHTDECAY)

        if optimizer_state != None:
            self.optimizer.load_state_dict(optimizer_state)

    ################## Forward operator Operators ######################
    def __forward__(self, images):
        """ Uses odl for forwarding images.

        Args:
            images: images, shape: (N, Resolution, Resolution)
        Returns:
            numpy.array: sinograms of input images.
        """
        return OperatorFunction.apply(self.radon, images).data[:,:, self.RESOLUTION // 2:self.RESOLUTION //2 + self.RESOLUTION]

    ################## Forward operator Operators ######################
    def __backward__(self, sinograms, with_gradient=False, deactivate_unet = False):
        """ Uses odl for forwarding images.

        Args:
            images: images, shape: (batch_size, N, Resolution)
        Returns:
            numpy.array: denoised sinograms of input images.
        """
        if with_gradient:
            fbp = self.fbp_with_gradient(self.pad(sinograms))
        else:
            fbp = OperatorFunction.apply(self.fbp, self.pad(sinograms)).data

        if self.UNET_REFINEMENT and not deactivate_unet:
            fbp = self.unet(fbp.view(-1, 1, self.RESOLUTION, self.RESOLUTION))

        return fbp

    @abstractmethod
    def _prepare_training_data(self, batch_size, loss):
        pass

    @abstractmethod
    def _prepare_training_images(self, images):
        pass

    def _prepare_validation_data(self, snr, batch_size):
        validation_dataset = ValidationDataset(self.V, snr, self.validation_sinograms, self.graph)
        loader = DataListLoader(dataset=validation_dataset, batch_size=batch_size, shuffle=False)
        return loader

    def __prepare_validation_images__(self, validation_images):
        self.V: int = validation_images.shape[0]
        self.T_validation_images = torch.from_numpy(validation_images).type(torch.float)
        self.validation_sinograms = self.__forward__(self.T_validation_images)

    def train(self, images=None, batch_size=1, loss=Loss.SINO):
        self.__execute_and_log_time__(lambda: self.__train__(images, batch_size, loss), "training")
        return self.model, self.optimizer, self.unet

    def __train__(self, images, batch_size, loss):
        self._prepare_training_images(images)
        self.model.train()

        for epoch in range(self.EPOCHS):
            # get training data, if snr and images are fixed, will only be calculated once
            loader, snr = self._prepare_training_data(batch_size, loss)
            loss_epoch = 0

            for data in loader:
                self.optimizer.zero_grad()
                out_sinograms = self.model(data)
                batch_n = int(out_sinograms.size(dim=0) / self.N)

                # if loss : sino
                # y = clean_sinograms
                # if loss : fbp
                # y = clean input images
                y = torch.cat([d.y for d in data]).to(out_sinograms.device)

                if loss == Loss.SINO:
                    loss_training = torch.linalg.norm(out_sinograms - y)
                elif loss == Loss.FBP:
                    fbps = self.__backward__(out_sinograms.view(batch_n, self.N, self.RESOLUTION), True)
                    loss_training = torch.linalg.norm(fbps.view(batch_n, self.RESOLUTION, self.RESOLUTION) - y.view(batch_n, self.RESOLUTION, self.RESOLUTION))
                else:
                    raise RuntimeError("Unknown loss type!")

                loss_training.backward()
                self.optimizer.step()
                loss_epoch += loss_training

            print(f"epoch : {epoch} , epoch-snr: {snr}, epoch_loss : {loss_epoch}")
            if self.USE_WANDB:
                wandb.log({
                    "epoch": epoch,
                    "epoch_loss": loss_epoch,
                    "epoch_snr": snr
                })

    def validate(self, validation_images, validation_snrs, batch_size, seed=2022):
        self.__execute_and_log_time__(lambda: self.__validate__(
            validation_images, validation_snrs, batch_size, seed), "validation")

    def __validate__(self, validation_images, validation_snrs, batch_size, seed):
        torch.manual_seed(seed)
        self.__prepare_validation_images__(validation_images)

        self.model.eval()
        self.model.to(self.device0)

        if self.unet != None:
            self.unet.to(self.device0)
            if self.UNET_TRAIN:
                self.unet.eval()

        counter = 0
        image_index = 0

        for snr in validation_snrs:
            loader = iter(self._prepare_validation_data(snr, batch_size))

            for validation_data in loader:
                denoised_sinograms = self.model(validation_data) # denoised sino
                batch_n = int(denoised_sinograms.size(dim=0) / self.N)

                denoised_sinograms = denoised_sinograms.view(batch_n, self.N, self.RESOLUTION)
                noisy_sinograms = torch.cat([d.x for d in validation_data]).view(batch_n, self.N, self.RESOLUTION).to(denoised_sinograms.device) # noisy sino
                clean_sinograms = torch.cat([d.y for d in validation_data]).view(batch_n, self.N, self.RESOLUTION).to(denoised_sinograms.device)

                fbps_denoised = self.__backward__(denoised_sinograms).to(denoised_sinograms.device)
                fbps_noisy = self.__backward__(noisy_sinograms, deactivate_unet=True).to(denoised_sinograms.device)

                clean_images = self.T_validation_images[image_index: image_index + batch_n,:,:].to(denoised_sinograms.device)
                image_index = image_index + batch_n

                for i in range(batch_n):
                    if self.USE_WANDB:
                        current_idx = counter
                        counter += 1

                        wandb.log({
                            "val_idx" : current_idx,
                            "val_loss_sino_denoised": torch.linalg.norm(denoised_sinograms[i]- clean_sinograms[i]),
                            "val_loss_sino_noisy": torch.linalg.norm(noisy_sinograms[i] - clean_sinograms[i]),

                            "val_loss_reco_denoised": torch.linalg.norm(fbps_denoised[i] - clean_images[i]),
                            "val_loss_reco_noisy": torch.linalg.norm(fbps_noisy[i] - clean_images[i]),

                            "val_snr_sino_denoised": find_SNR(clean_sinograms[i], denoised_sinograms[i]),
                            "val_snr_sino_noisy": find_SNR(clean_sinograms[i], noisy_sinograms[i]),

                            "val_snr_reco_denoised": find_SNR(clean_images[i], fbps_denoised[i]),
                            "val_snr_reco_noisy": find_SNR(clean_images[i], fbps_noisy[i])
                        })

                        if counter < 100:
                            wandb.log({
                                "val_clean" : wandb.Image(clean_images[i].cpu().detach().numpy(), caption=f"Original object"),
                                "val_reco_denoised": wandb.Image(fbps_denoised[i].cpu().detach().numpy(), caption=f"Rec denoised - SNR: {snr} "),
                                "val_reco_noisy": wandb.Image(fbps_noisy[i].cpu().detach().numpy(), caption=f"Rec noisy - SNR: {snr}")
                            })

                torch.cuda.empty_cache()
            image_index = 0

    def finish_wandb(self, execution_time=None):
        if execution_time != None:
            wandb.log({"execution_time": execution_time})

        wandb.finish()

    def __execute_and_log_time__(self, action, name):
        if self.VERBOSE:
            t = time.time()

        action()

        if self.VERBOSE:
            self.time_dict[name] = time.time()-t
            print(f"{name} finished in :", self.time_dict[name])

            if self.USE_WANDB:
                wandb.log({f"{name}": self.time_dict[name]})

########################### Implementations ############################

class GatDenoiserToyImagesDynamic(GatBase):
    """ Special toy images denoiser.
        This denoiser will generate new images in every epoch.
    """
    def _prepare_training_images(self, images):
        return

    def _prepare_training_data(self, batch_size, loss):
        images = draw_uniform_toyimages(self.RESOLUTION, 13, self.M)
        sinos = self.__forward__(torch.tensor(images).type(torch.float))

        if self.FIXED_SNR:
            snr = self.SNR
        else:
            snr = torch.randint(self.SNR_LOWER, self.SNR_UPPER, (1,))

        noisy_sinos = add_noise_to_sinograms(sinos, snr)

        if loss == Loss.SINO:
            dataset = SinogramToSinogramDataset(self.M, sinos, noisy_sinos, self.graph)
        elif loss == Loss.FBP:
            dataset = SinogramToImageDataset(self.M, images, noisy_sinos, self.graph)
        else:
            raise RuntimeError("Unknown loss value!")

        loader = DataListLoader(dataset=dataset, batch_size=batch_size, shuffle=True)
        return loader, snr

class GatDenoiserImagesFixed(GatBase):
    """ GatDenoiser for fixed images during training.
        During training, in every epoch the same images are passed to the model.
        Clean sinograms need to be only calculated once, if SNR is fixed,
        noisy sinograms are calculated only once as well.
    """
    def _prepare_training_images(self, images):
        self.training_images = torch.tensor(images).type(torch.float)
        self.training_sinos = self.__forward__(self.training_images)
        self.M: int = images.shape[0]

    def _prepare_training_data(self, batch_size, loss):
        if self.FIXED_SNR:
            if self.loader is None:
                self.training_sinos_noisy = add_noise_to_sinograms(self.training_sinos, self.SNR)

                if loss == Loss.SINO:
                    self.dataset = SinogramToSinogramDataset(self.M, self.training_sinos, self.training_sinos_noisy, self.graph)
                elif loss == Loss.FBP:
                    self.dataset = SinogramToImageDataset(self.M, self.training_images, self.training_sinos_noisy, self.graph)
                else:
                    raise RuntimeError("Unknown loss value!")

                self.loader = DataListLoader(dataset=self.dataset, batch_size=batch_size, shuffle=True)

            return self.loader, self.SNR
        else:
            snr = torch.randint(self.SNR_LOWER, self.SNR_UPPER, (1,))
            noisy_sinograms = add_noise_to_sinograms(self.training_sinos, snr)
            if loss == Loss.SINO:
                dataset = SinogramToSinogramDataset(self.M, self.training_sinos, noisy_sinograms, self.graph)
            elif loss == Loss.FBP:
                dataset = SinogramToImageDataset(self.M, self.training_images, noisy_sinograms, self.graph)
            else:
                raise RuntimeError("Unknown loss value!")

            loader = DataListLoader(dataset=dataset, batch_size=batch_size, shuffle=True)
            return loader, snr

class GatValidator(GatBase):
    """ GatValidator class which is only able to run validator from given trained model.
        Validator can be logged to wandb as well.
    """
    def _prepare_training_images(self, images):
         raise RuntimeError("Validator cannot be trained")

    def _prepare_training_data(self, batch_size, images=None):
         raise RuntimeError("Validator cannot be trained")


# https://github.com/thiswillbeyourgithub/i3_search_anki_collection
# Work under GPL v3 license
import urllib.request
import json
import argparse

def ankiconnect(action, **params):
    def request_wrapper(action, **params):
        return {'action': action, 'params': params, 'version': 6}

    requestJson = json.dumps(request_wrapper(action, **params)
                             ).encode('utf-8')
    try:
        response = json.load(urllib.request.urlopen(
                                urllib.request.Request(
                                    'http://localhost:8765',
                                    requestJson)))
    except (ConnectionRefusedError, urllib.error.URLError) as e:
        print(f"{e}: is Anki open and ankiconnect enabled?")
        raise SystemExit()

    if len(response) != 2:
        raise Exception('response has an unexpected number of fields')
    if 'error' not in response:
        raise Exception('response is missing required error field')
    if 'result' not in response:
        raise Exception('response is missing required result field')
    if response['error'] is not None:
        raise Exception(response['error'])
    return response['result']

parser = argparse.ArgumentParser()
parser.add_argument("--query",
                    nargs=1,
                    metavar="QUERY",
                    dest='query',
                    type=str,
                    required=True,
                    help="Query to ask to anki")
args = parser.parse_args().__dict__

ankiconnect(action="guiBrowse", query=args["query"][0])


# rankDataRequestServer.py

import sys
import blpapi
import datetime

import blpapi
import sys
import datetime
import os

# for additional DEBUG logging
#os.environ['BLPAPI_LOGLEVEL'] = 'DEBUG'

SESSION_STARTED                 = blpapi.Name("SessionStarted")
SESSION_STARTUP_FAILURE         = blpapi.Name("SessionStartupFailure")
SESSION_CONNECTION_UP           = blpapi.Name("SessionnConnectionUp")
SESSION_CONNECTION_DOWN         = blpapi.Name("SessionConnectionDown")
SESSION_TERMINATED              = blpapi.Name("SessionTerminated")

SERVICE_OPENED                  = blpapi.Name("ServiceOpened")
SERVICE_OPEN_FAILURE            = blpapi.Name("ServiceOpenFailure")
SERVICE_DOWN                    = blpapi.Name("ServiceDown")

AUTHORIZATION_SUCCESS           = blpapi.Name("AuthorizationSuccess")
AUTHORIZATION_FAILURE           = blpapi.Name("AuthorizationFailure")

SLOW_CONSUMER_WARNING           = blpapi.Name("SlowConsumerWarning")
SLOW_CONSUMER_WARNING_CLEARED   = blpapi.Name("SlowConsumerWarningCleared")

EXCEPTION                       = blpapi.Name("Exception")
REPORT                          = blpapi.Name("Report")

d_rank = "//blp/rankapi"
d_auth = "//blp/apiauth"
d_host = "10.137.23.19"             # IP address of the Trading API Server instance.
d_port = 8194
d_user = "RANKAPI\\SERVER_API"      # User name of the server-side user.
d_ip = "1.1.1.1"                    # Any IP address unique for this user.

bEnd=False

class SessionEventHandler():

    def sendAuthRequest(self,session):

        authService = session.getService(d_auth)
        authReq = authService.createAuthorizationRequest()
        authReq.set("emrsId",d_user)
        authReq.set("ipAddress", d_ip)
        self.identity = session.createIdentity()

        print ("Sending authorization request: %s" % (authReq))

        session.sendAuthorizationRequest(authReq, self.identity)

        print ("Authorization request sent.")

    def sendCreateOrder(self, session):

        service = session.getService(d_rank)

        # build request
        request = service.createRequest("Query")

        ### set date/time range
        request.set("start", datetime.datetime(2021, 3, 1, 0, 0, 0, 0))
        request.set("end", datetime.datetime(2021, 3, 2, 0, 0, 0, 0))

        ### group by 0=Broker , 1=Security
        #request.set("groupBy", "Broker")
        request.set("groupBy", "Security")

        ### exchanges or securities
        # exchanges can be set using Bloomberg exchange code
        #exchanges = request.getElement("securityCriteria").setChoice("exchanges")
        #exchange = exchanges.appendElement()
        #exchange.setElement("code", "US");

        ### securities can be set to either Bloomberg ticker or figi
        securities = request.getElement("securityCriteria").setChoice("securities")
        securities.appendElement().setElement("ticker", "AAPL US Equity")
        securities.appendElement().setElement("ticker", "MSFT US Equity")
        securities.appendElement().setElement("ticker", "VOD LN Equity")

        #security.setElement("figi", "BBG000B9XRY4"); # figi for AAPL US Equity

        ### specify broker by acronym or rank
        #broker = request.getElement("brokers").appendElement()
        #broker.setElement("acronym", "ABCD"); # acronym

        ### source enum 0=Broker Contributed
        request.set("source", "Broker Contributed")

        ### units enum 0=Shares, 1=Local, 2=USD, 3=EUR, 4=GBP
        request.set("units", "Shares")

        print ("Sending Request: %s" % request.toString())

        #self.requestID = session.sendRequest(request)

        self.requestID = blpapi.CorrelationId()
        session.sendRequest(request, identity=self.identity, correlationId=self.requestID)  #Note the addition of the identity object.

        print ("RANK data request sent.")

    def processAdminEvent(self,event):
        print("Processing ADMIN event")

        for msg in event:
            if msg.messageType() == SLOW_CONSUMER_WARNING:
                print("Warning: Entered Slow Consumer status")

            elif msg.messageType() == SLOW_CONSUMER_WARNING_CLEARED:
                print("Slow consumer status cleared")

            else:
                print(msg)

    def processSessionStatusEvent(self,event,session):
        print("Processing SESSION_STATUS event")

        for msg in event:
            if msg.messageType() == SESSION_STARTED:
                print("Session started...")
                session.openServiceAsync(d_auth)

            elif msg.messageType() == SESSION_STARTUP_FAILURE:
                print("Error: Session startup failed", file=sys.stderr)

            else:
                print(msg)

    def processServiceStatusEvent(self,event,session):
        print ("Processing SERVICE_STATUS event")

        for msg in event:

            if msg.messageType() == SERVICE_OPENED:

                serviceName = msg.asElement().getElementAsString("serviceName")

                print("Service opened [%s]" % (serviceName))

                if serviceName==d_auth:

                    print("Auth service opened... Opening EMSX service...")
                    session.openServiceAsync(d_rank)

                elif serviceName==d_rank:

                    print("RANK service opened... Sending authorization request...")

                    self.sendAuthRequest(session)

            elif msg.messageType() == SERVICE_OPEN_FAILURE:
                    print("Error: Service Failed to open", file=sys.stderr)

    def processAuthorizationStatusEvent(self,event):

        print ("Processing AUTHORIZATION_STATUS event")

        for msg in event:

            print("AUTHORIZATION_STATUS message: %s" % (msg))

    def processResponseEvent(self, event, session):
        print("Processing RESPONSE event")

        for msg in event:

            print("MESSAGE: %s" % msg.toString())
            print("CORRELATION ID: %d" % msg.correlationIds()[0].value())

            if msg.messageType() == AUTHORIZATION_SUCCESS:
                print("Authorization successful....")
                print ("SeatType: %s" % (self.identity.getSeatType()))
                self.sendCreateOrder(session)

            elif msg.messageType() == AUTHORIZATION_FAILURE:
                print("Authorization failed....", file=sys.stderr)
                # insert code here to automatically retry authorization...

            elif msg.correlationIds()[0].value() == self.requestID.value():
                print("MESSAGE TYPE: %s" % msg.messageType())

                if msg.messageType() == EXCEPTION:
                    print (msg)
                    print ("Exception occured")

                elif msg.messageType() == REPORT:
                    ts = msg.getElementAsDatetime("timestampUtc")
                    print ("Timestamp: ", ts)
                    print(msg)

                global bEnd
                bEnd = True
            else:
                print ("Unexpected message...")
                print (msg)

    def processMiscEvents(self, event):

        print("Processing " + event.eventType() + " event")

        for msg in event:

            print("MISC MESSAGE: %s" % (msg.tostring()))

    def processEvent(self, event, session):
        try:

            if event.eventType() == blpapi.Event.ADMIN:
                self.processAdminEvent(event)

            if event.eventType() == blpapi.Event.SESSION_STATUS:
                self.processSessionStatusEvent(event,session)

            elif event.eventType() == blpapi.Event.SERVICE_STATUS:
                self.processServiceStatusEvent(event,session)

            elif event.eventType() == blpapi.Event.AUTHORIZATION_STATUS:
                self.processAuthorizationStatusEvent(event)

            elif event.eventType() == blpapi.Event.RESPONSE:
                self.processResponseEvent(event,session)

            else:
                self.processMiscEvents(event)

        except:
            print("Exception:  %s" % sys.exc_info()[0])

        return False

def main():

    sessionOptions = blpapi.SessionOptions()
    sessionOptions.setServerHost(d_host)
    sessionOptions.setServerPort(d_port)

    print("Connecting to %s:%d" % (d_host,d_port))

    eventHandler = SessionEventHandler()

    session = blpapi.Session(sessionOptions, eventHandler.processEvent)

    if not session.startAsync():
        print("Failed to start session.", file=sys.stderr)
        return

    global bEnd
    while bEnd==False:
        pass

    print ("Terminating...")

    session.stop()

if __name__ == "__main__":
    print("Bloomberg - RANK API Example - Server - rankDataRequestServer")
    try:
        main()
    except KeyboardInterrupt:
        print("Ctrl+C pressed. Stopping...")

__copyright__ = """
Copyright 2022. Bloomberg Finance L.P.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:  The above
copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
"""


import argparse
import os
import time

import gym
import gym_minigrid
from gym_minigrid.wrappers import ImgObsWrapper, FullyObsWrapper
import torch
import numpy as np

import parl_minigrid
from parl_minigrid.envs.wrappers import EpisodeTerminationWrapper

from stable_baselines3.common.monitor import Monitor
from stable_baselines3.dqn.policies import DQNPolicy

from parl_agents.tuning.utils import TuneObjectiveCallback
from parl_agents.dqn.ddqn import DDQN
from parl_agents.nn_models.babyai_cnn import BabyAIFullyObsSmallCNN
from parl_agents.wrappers.step_cost import MiniGridCostWrapper, DQNRewardWrapper

import optuna
from optuna.integration.skopt import SkoptSampler
from optuna.pruners import BasePruner, MedianPruner, SuccessiveHalvingPruner
from optuna.samplers import BaseSampler, RandomSampler, TPESampler

N_TS=1
N_ES=1

# parameter sampler
def sample_params(trial: optuna.Trial):
    learning_rate = trial.suggest_loguniform("lr", 1e-5, 1e-3)
    buffer_size = trial.suggest_categorical("buffer_size", [1024, 4096, 10240, 40960, 102400, 409600])
    learning_starts = trial.suggest_int("learning_starts", 8, 10240)
    batch_size = trial.suggest_categorical("batch_size", [8, 16, 32, 64, 128, 256])
    train_freq = trial.suggest_int("train_freq", 4, 4096)
    gradient_steps = trial.suggest_int("gradient_steps", 1, 256)
    target_update_interval = trial.suggest_uniform("target_update_interval", 1, 40960)
    exploration_fraction = trial.suggest_uniform("exploration_fraction", 0.01, 0.2)
    exploration_final_eps = trial.suggest_loguniform("exploration_final_eps", 0.01, 0.2)

    return {
        "learning_rate": learning_rate,
        "buffer_size": buffer_size,
        "learning_starts":learning_starts,
        "batch_size": batch_size,
        "train_freq": train_freq,
        "gradient_steps": gradient_steps,
        "target_update_interval": target_update_interval,
        "exploration_fraction": exploration_fraction,
        "exploration_final_eps": exploration_final_eps,
    }

def create_env(args):
    train_env = gym.make(args.env, train_mode=True, max_steps=1024, num_train_seeds=N_TS, num_test_seeds=N_ES)
    train_env = FullyObsWrapper(train_env)
    train_env = ImgObsWrapper(train_env)
    train_env = MiniGridCostWrapper(train_env)
    train_env = DQNRewardWrapper(train_env, reward_scale=100, step_cost=0)
    train_env = EpisodeTerminationWrapper(train_env)
    train_env = Monitor(train_env)
    return train_env

class ParameterTuner:
    def __init__(self, args, hyper_params):
        self.study_name = " ".join([args.study_name, args.env])
        self.n_trials = args.trials
        self.n_timesteps = args.timesteps
        self.hyper_params = hyper_params
        self.args = args

    def objective(self, trial: optuna.Trial) -> float:
        train_env = create_env(self.args)
        self.hyper_params.update(sample_params(trial))

        # create agent
        policy_kwargs = dict(
            features_extractor_class = BabyAIFullyObsSmallCNN,
            features_extractor_kwargs = dict(features_dim=128),
            net_arch = [128, 128],
            normalize_images = False
        )

        model = DDQN(
            policy=DQNPolicy,
            env=train_env,
            tau=1.0,
            gamma=0.99,
            exploration_initial_eps=1.0,
            max_grad_norm=10,
            tensorboard_log=None,
            policy_kwargs=policy_kwargs,
            verbose=0,
            **self.hyper_params
        )
        model.trial = trial

        # call back for getting objective value
        tune_obj_call_back = TuneObjectiveCallback(trial)
        try:
            model.learn(total_timesteps=self.n_timesteps, callback=tune_obj_call_back)
            model.env.close()
        except AssertionError as e:
            model.env.close()
            print(e)
            raise optuna.exceptions.TrialPruned()

        is_pruned = tune_obj_call_back.is_pruned
        mean_ep_length = tune_obj_call_back.mean_ep_length

        del model.env
        del model

        if is_pruned:
            raise optuna.exceptions.TrialPruned()

        return mean_ep_length

    def hyperparam_optimization(self):
        self.tensorboard_log = None

        sampler = TPESampler(n_startup_trials=2, seed=0)
        pruner = MedianPruner(n_startup_trials=2, n_warmup_steps=0)
        study = optuna.create_study(
            sampler=sampler,
            pruner=pruner,
            study_name=self.study_name,
            load_if_exists=True,
            direction="minimize",
        )
        try:
            study.optimize(self.objective, n_trials=self.n_trials)
        except KeyboardInterrupt:
            pass

        print("Number of finished trials: ", len(study.trials))
        print("Best trial:")
        trial = study.best_trial
        print("Value: ", trial.value)
        print("Params: ")
        for key, value in trial.params.items():
            print(f"    {key}: {value}")
        report_name = (
            f"report_{self.study_name}_steps-{self.n_timesteps}_{int(time.time())}.csv"
        )
        log_path = os.path.join(os.path.dirname(__file__), report_name)
        # Write report
        os.makedirs(os.path.dirname(log_path), exist_ok=True)
        study.trials_dataframe().to_csv(log_path)

if __name__ == "__main__":
    print("cuda.is_available:{}".format(torch.cuda.is_available()))
    print("cuda.device_count:{}".format(torch.cuda.device_count()))
    if torch.cuda.is_available():
        print("cuda.current_device:{}".format(torch.cuda.current_device()))
        print("cuda.get_device_name:{}".format(torch.cuda.get_device_name(torch.cuda.current_device())))
    else:
        print("no cuda device")

    parser = argparse.ArgumentParser()
    parser.add_argument("--study_name", type=str, default="ddqn tuning")
    parser.add_argument("--env", type=str, default="MazeRooms-8by8-DoorKey-v0")
    parser.add_argument("--timesteps", type=int, default=1000000)
    parser.add_argument("--trials", type=int, default=100)
    args = parser.parse_args()

    hyper_params = {}
    exp_manager = ParameterTuner(args, hyper_params)
    exp_manager.hyperparam_optimization()


from datetime import timedelta, datetime, timezone
from dateutil import parser

""" Used for testing.
"""
EVALUATION_TIME_OVERRIDE = None

class SunriseSunsetData(object):

    def __init__(self, results):
        self.sunrise = parser.isoparse(results['results']['sunrise'])
        self.sunset = parser.isoparse(results['results']['sunset'])
        self.civil_twilight_begin = parser.isoparse(results['results']['civil_twilight_begin'])
        self.civil_twilight_end = parser.isoparse(results['results']['civil_twilight_end'])
        self.nautical_twilight_begin = parser.isoparse(results['results']['nautical_twilight_begin'])
        self.nautical_twilight_end = parser.isoparse(results['results']['nautical_twilight_end'])
        self.astronomical_twilight_begin = parser.isoparse(results['results']['astronomical_twilight_begin'])
        self.astronomical_twilight_end = parser.isoparse(results['results']['astronomical_twilight_end'])

        self.sunrise_begin = self.civil_twilight_begin
        self.sunrise_length = self.sunrise - self.sunrise_begin
        self.sunrise_end = self.sunrise

        self.sunset_begin = self.sunset
        self.sunset_end = self.civil_twilight_end
        self.sunset_length = self.sunset_end - self.sunset
        self.avg_transition_time = timedelta(seconds=(self.sunrise_length.seconds + self.sunset_length.seconds) / 2)

    @property
    def utcnow(self):
        return EVALUATION_TIME_OVERRIDE or datetime.now(tz=timezone.utc)

    @property
    def is_daylight(self):
        """ True from the the begining of civil_twilight_begin until until sunset
        """
        return self.utcnow >= self.civil_twilight_begin and self.utcnow < self.sunset

    @property
    def is_nighttime(self):

        return self.utcnow >= self.sunset or self.utcnow < self.civil_twilight_begin

    @property
    def is_twilight(self):

        if (self.utcnow >= self.civil_twilight_begin and self.utcnow <= self.sunrise):
            return True

        if (self.utcnow >= self.sunset_begin and self.utcnow <= self.civil_twilight_end):
            return True

        return False

    @property
    def proportion(self):
        # civil_twilight_begin > sunrise > day > sunset > civil_twilight_end
        if (self.utcnow >= self.sunrise_begin and self.utcnow <= self.sunrise_end):
            time_left = self.sunrise_end - self.utcnow
            total_time = self.sunrise_length
            return 1 - (time_left / total_time)
        elif (self.utcnow >= self.sunset_begin and self.utcnow <= self.sunset_end):
            time_left = self.sunset_end - self.utcnow
            total_time = self.sunset_length
            return 1 - (time_left / total_time)
        else:
            return 1.0

    def __str__(self):
        return "utcnow={}, civil_twilight_begin={}, sunrise={}, sunset={}, sunset_end={}, is_daylight={}, is_nightlight={}, is_twilight={}, proportion={}".format(self.utcnow.strftime("%c"), self.civil_twilight_begin, self.sunrise, self.sunset, self.civil_twilight_end, self.is_daylight, self.is_nighttime, self.is_twilight, self.proportion)

    def to_json(self):
        return {'utcnow': self.utcnow,
                'civil_twilight_begin': self.civil_twilight_begin,
                'sunrise': self.sunrise,
                'sunset': self.sunset,
                'civil_twilight_end': self.civil_twilight_end,
                'is_daylight': self.is_daylight,
                'is_twilight': self.is_twilight,
                'is_nighttime': self.is_nighttime,
                'sunrise_length': self.sunrise_length,
                'sunset_length': self.sunset_length
                }


from datetime import date
atual=date.today().year
nac=int(input("digiti sua data de nacimento"))
idade = atual - nac
print("O atleta tem {}".format(idade))
if idade <= 9:
    print("mirim")
elif idade<=14:
    print("infantil")
elif  idade<19:
    print("junior")
elif idade<=20:
    print("senio")
elif idade>20:
    print("master")


import json
from operator import methodcaller
from unittest import result
from sqlalchemy import column
import tabula
import pandas as pd
from flask import Flask, request,send_file,jsonify
from flask_cors import CORS
from insertTables import pushTablesToExcel
from pandasql import sqldf
from pdfmerge import combinePdf
import base64
import string
import io
import random
import os
import shutil

app = Flask(__name__)
CORS(app)

# db_connection = connectToDB()
# cursor = db_connection[0]
# my_conn = db_connection[1]

@app.route('/')
def mainPage():
    return "Server started...."

@app.route('/uploadFileName',methods = ['GET'])
def uploadFileName():
    global filename
    filename = request.args.getlist('filename')[0]
    return "GET filename"

@app.route('/upload',methods =['POST'])
def upload():
    str = request.files['file']
    str.save('C:/project/files/{}'.format(filename))
    #pushTablesToDB(my_conn)
    dfs = pushTablesToExcel(filename)
    return "POST hitted..."

@app.route('/search',methods = ['GET'])
def search():
        args = request.args
        hs_code = args.getlist('code')
        print(hs_code[0])
        dfs = pd.read_excel('C:/project/files/DB.xlsx')
        column_names = dfs.columns.values.tolist()
        if(column_names.count('HS_Code') == 0):
            return "Not found"
        query1 = 'select * from dfs where HS_Code = \'{}\''.format(hs_code[0])
        ans = sqldf(query1)

        if(len(ans) == 0):
            return "Not found"
        # dic=ans.to_dict('dict')
        # print(dic)
        dic1 = ans.to_dict('records')
        print(dic1)
        # print(dict_temp)
        # cursor.execute(query)
        # records = cursor.fetchall()
        # print('total rows found : ',len(records))
        # if(len(records) == 0):
        #     print('No matches found')
        # else:
        #     for row in records:
        #         print(str(row))
        return json.dumps(dic1)

@app.route('/pdfviewer',methods = ['GET'])
def pdfviewer():
    args = request.args
    searchfilename = args.getlist('searchResfilename')[0]
    with open ("C:/project/files/{}".format(searchfilename),"rb") as pdf_file:
        img = base64.b64encode(pdf_file.read())
    return send_file(io.BytesIO(img),attachment_filename="hello.pdf",as_attachment=True)

@app.route('/mergeUpload',methods =['POST'])
def mergeUpload():
    str = request.files['pdfs']
    res = ''.join(random.choices(string.ascii_uppercase +
                             string.digits, k = 6))
    str.save('C:/project/mergedirectory/test{}.pdf'.format(res))
    # combinePdf()
    return "saved..."

@app.route('/combine',methods=['GET'])
def combine():
   combinePdf()
   dir = 'C:\project\mergedirectory'
   for f in os.listdir(dir):
        os.remove(os.path.join(dir,f))

   with open ("C:/project/backend-flask/final.pdf","rb") as pdf_file:
        img = base64.b64encode(pdf_file.read())
   return send_file(io.BytesIO(img),attachment_filename="hello.pdf",as_attachment=True)

app.run(debug=True)


# -*- coding: utf-8 -*-
from gcd import ext_gcd
from montgomery import exp_mod
import strconvert
import sympy
from prettytable import PrettyTable

#中國於是定理(兩式)
def crt (a1,a2,m1,m2):
    M = m1*m2
    M1 = M//m1
    M2 = M//m2
    M1_inv = ext_gcd(M1 , m1)[1]
    M2_inv = ext_gcd(M2 , m2)[1]

    if(M1_inv < 0):M1_inv = M1_inv+m1
    if(M2_inv < 0):M2_inv = M2_inv+m2

    n = (a1*M1*M1_inv+a2*M2*M2_inv)%M
    return n

def gen_key(p, q):
    n = p * q
    return (n), (p ,q)    #回傳公鑰<n>、私鑰<p,q>

# 將明文m加密成密文c
def encrypt(m, pubkey):
    n = pubkey
    c = exp_mod(m, 2, n)
    return c

# 將密文m解密成明文c
def decrypt(c, prikey):
    p = prikey[0]
    q = prikey[1]
    a1 = exp_mod(c, (p+1)//4 , p)
    a2 = p - a1
    b1 = exp_mod(c, (q+1)//4 , q)
    b2 = q - b1
    m = crt(a1,b1,p,q),crt(a1,b2,p,q),crt(a2,b1,p,q),crt(a2,b2,p,q)
    return m

if __name__ == "__main__":

    while(True):
        choose = int(input("請選擇Rabin模式\n=====================================\n1.加密\n2.解密\n3.離開\n=====================================\n"))

        if(choose == 1):

            #明文輸入
            text = input("請輸入預加密明文:\n")
            m = strconvert.str_con(text)

            #生成p、q
            while(True):

                print("請輸入p、q (p*q >",m,")")

                while(True):
                    p = int(input('請輸入p:\n'))
                    if(sympy.isprime(p) == False):
                        print(p,"不是質數")
                        continue
                    elif((p-3)%4 != 0): #p、q必須滿足4k+3
                        print("p(",p,")不滿足4k+3")
                    else:
                        break

                while(True):
                    q = int(input('請輸入q:\n'))
                    if(sympy.isprime(q) == False):
                        print(q,"不是質數")
                        continue
                    elif((q-3)%4 != 0): #p、q必須滿足4k+3
                        print("q(",q,")不滿足4k+3")
                        continue
                    else:
                        break

                if(p*q < m):
                        print("p*q < m，請重新輸入")
                        continue
                else:
                    break

            #生成公鑰<n>、私鑰<n,p,q>
            pubkey, prikey = gen_key(p, q)
            print(pubkey, prikey)

            #Rabin加密
            c = encrypt(m, pubkey)

            print("\n------------- 生成公鑰 ---------------\nn =",pubkey,"\n")
            print("------------- 生成私鑰 ---------------\np =",prikey[0],"\nq =",prikey[1],"\n")
            print("------------- 加密密文 ---------------\n",c,"\n")

        elif(choose == 2):

            c = int(input("請輸入預解密密文\n"))

            while(True):
                p = int(input('請輸入p\n'))
                if(sympy.isprime(p) == False):
                    print("p不是質數")
                    continue
                elif((p-3)%4 != 0): #p、q必須滿足4k+3
                    print("p(",p,")不滿足4k+3")
                else:
                    break

            while(True):
                q = int(input('請輸入q\n'))
                if(sympy.isprime(q) == False):
                    print("q不是質數")
                    continue
                elif((q-3)%4 != 0): #p、q必須滿足4k+3
                    print("q(",q,")不滿足4k+3")
                    continue
                else:
                    break

            prikey = (p,q,p*q)

            #Rabin解密
            d = decrypt(c, prikey)
            print("\n------------- 解密明文 ---------------")
            print("",d[0],"(",strconvert.str_re(d[0]),")")
            print("-------------------------------------\n",d[1],"(",strconvert.str_re(d[1]),")")
            print("-------------------------------------\n",d[2],"(",strconvert.str_re(d[2]),")")
            print("-------------------------------------\n",d[3],"(",strconvert.str_re(d[3]),")\n")

        elif(choose == 3):
            break


import sys
sys.stdin = open("3055in.txt",'r')

from collections import deque
R,C = map(int,input().split())
List = list(list( x for x in input()) for _ in range(R))
waters = deque([])
hedgehog = deque([])
dy = [-1,1,0,0]
dx = [0,0,-1,1]

for y in range(R):
    for x in range(C):
        if List[y][x] == "*":
            waters.append((y,x))
        elif List[y][x] == "S":
            hedgehog.append((y,x))

cnt = 0
def fillWaters(y,x):
    global visitedWater
    for i in range(4):
        checkY = y+dy[i]
        checkX = x+dx[i]
        if 0<=checkY<R and 0<=checkX<C and visitedWater[checkY][checkX] and (List[checkY][checkX]=="." or List[checkY][checkX]=="S"):
            List[checkY][checkX]="*"
            waters.append((checkY,checkX))

def goBeaver(y,x):

    pass

visitedWater = list(list(True for _ in range(C)) for _ in range(R))
visitedBeaver = list(list(True for _ in range(C)) for _ in range(R))
answer = 0
flag = 1
while flag:
    answer += 1
    for _ in range(len(waters)):
        Y,X = waters.popleft()
        fillWaters(Y,X)
    for _ in range(len(hedgehog)):
        Y,X = hedgehog.popleft()
        for i in range(4):
            newY = Y + dy[i]
            newX = X + dx[i]
            if 0<=newY<R and 0<=newX<C and visitedBeaver[newY][newX]:
                visitedBeaver[newY][newX] = False
                if List[newY][newX] == ".":
                    List[newY][newX] = "S"
                    hedgehog.append((newY,newX))
                if List[newY][newX] == "D":
                    hedgehog.append(("end"))
                    flag = 0
                    break

    if len(hedgehog):
        pass
    else:
        break
if hedgehog:
    print(answer)
else:
    print("KAKTUS")


import os,sys
import numpy as np
from mpi4py import MPI
import pickle
import random as rand

from py_mc.mc import *

def RX_MPI_init():
    args = sys.argv
    nreplicas = int(args[1] )
    nprocs_per_replica = int(args[2])
    commworld = MPI.COMM_WORLD
    worldrank = commworld.Get_rank()
    worldprocs = commworld.Get_size()
    rand_seeds = [rand.random() for i in range(worldprocs)]
    rand.seed(rand_seeds[worldrank])

    if worldprocs > nreplicas:
        if worldrank == 0:
            print("Setting number of replicas smaller than MPI processes; I hope you"
                  +" know what you're doing..."
            )
            sys.stdout.flush()
        if worldrank >= nreplicas:
            # belong to comm that does nothing
            comm = commworld.Split(color=1, key=worldrank)
            comm.Free()
            sys.exit() # Wait for MPI_finalize
        else:
            comm = commworld.Split(color=0, key=worldrank)
    else:
        comm = commworld
    return comm, nreplicas, nprocs_per_replica

class ParallelMC(object):
    def __init__(self, comm, MCalgo, model, configs, kTs, grid=None, subdirs=True):
        self.comm = comm
        self.rank = self.comm.Get_rank()
        self.procs = self.comm.Get_size()
        self.kTs = kTs
        self.model = model
        self.subdirs = subdirs
        self.nreplicas = len(configs)

        if not(self.procs == self.nreplicas == len(self.kTs)):
            if self.rank==0:
                print("ERROR: You have to set the number of replicas equal to the"
                      +"number of temperatures equal to the number of processes"
                )
            sys.exit(1)

        myconfig = configs[self.rank]
        mytemp = kTs[self.rank]
        self.mycalc = MCalgo(model, mytemp, myconfig, grid)

    def run(self, nsteps, sample_frequency, observer=observer_base()):
        if self.subdirs:
            # make working directory for this rank
            try:
                os.mkdir(str(self.rank))
            except FileExistsError:
                pass
            os.chdir(str(self.rank))
        observables = self.mycalc.run(nsteps, sample_frequency, observer)
        pickle.dump(self.mycalc.config, open("config.pickle","wb"))
        if self.subdirs: os.chdir("../")
        if sample_frequency:
            obs_buffer = np.empty([self.procs,len(observables)])
            self.comm.Allgather(observables, obs_buffer)
            return obs_buffer

class TemperatureRX_MPI(ParallelMC):
    def __init__(self, comm, MCalgo, model, configs, kTs, grid=None,  subdirs=True):
        super(TemperatureRX_MPI, self).__init__(comm, MCalgo, model, configs, kTs, grid, subdirs)
        self.betas = 1.0/np.array(kTs)
        self.rank_to_T = np.arange(0, self.procs, 1, dtype=np.int)
        self.float_buffer = np.array(0.0, dtype=np.float)
        self.int_buffer = np.array(0, dtype=np.int)
        self.obs_save = []
        self.Trank_hist = []
        self.kT_hist = []

    def reload(self):
        self.rank_to_T = pickle.load(open("rank_to_T.pickle","rb"))
        self.mycalc.kT = self.kTs[self.rank_to_T[self.rank]]
        self.mycalc.config = pickle.load(open(str(self.rank)+"/calc.pickle","rb"))
        self.obs_save0 = np.load(open(str(self.rank)+"/obs_save.npy","rb"))
        self.Trank_hist0 = np.load(open(str(self.rank)+"/Trank_hist.npy","rb"))
        self.kT_hist0 = np.load(open(str(self.rank)+"/kT_hist.npy","rb"))

    def find_procrank_from_Trank(self, Trank):
        i = np.argwhere(self.rank_to_T == Trank)
        if i == None:
            sys.exit("Internal error in TemperatureRX_MPI.find_procrank_from_Trank")
        else:
            return i

    def Xtrial(self, XCscheme=-1):
        # What is my temperature rank?
        myTrank = self.rank_to_T[self.rank]
        if (myTrank+XCscheme)%2 == 0 and myTrank == self.procs - 1:
            self.comm.Allgather(self.rank_to_T[self.rank], self.rank_to_T)
            return
        if XCscheme == 1 and myTrank == 0:
            self.comm.Allgather(self.rank_to_T[self.rank], self.rank_to_T)
            return
        if (myTrank+XCscheme)%2 == 0:
            myTrankp1 = myTrank + 1
            # Get the energy from the replica with higher temperature
            exchange_rank = self.find_procrank_from_Trank(myTrankp1)
            self.comm.Recv(self.float_buffer, source=exchange_rank, tag=1)
            delta = (self.betas[myTrankp1] - self.betas[myTrank]) \
                    * (self.mycalc.energy - self.float_buffer)
            if delta < 0.0:
                # Exchange temperatures!
                self.comm.Send([self.rank_to_T[self.rank],1,MPI.INT], dest=exchange_rank, tag=2)

                self.rank_to_T[self.rank] = myTrankp1
            else:
                accept_probability = exp(-delta)
                #print accept_probability, "accept prob"
                if random() <= accept_probability:
                    self.comm.Send([self.rank_to_T[self.rank],1,MPI.INT], dest=exchange_rank, tag=2)
                    self.rank_to_T[self.rank] = myTrankp1
                else:
                    #print("RXtrial rejected")
                    self.comm.Send([self.rank_to_T[exchange_rank],1,MPI.INT], dest=exchange_rank, tag=2)
        else:
            myTrankm1 = myTrank - 1
            exchange_rank = self.find_procrank_from_Trank(myTrankm1)
            self.comm.Send(self.mycalc.energy, dest=exchange_rank, tag=1)
            self.comm.Recv([self.int_buffer,1,MPI.INT],source=exchange_rank, tag=2)
            self.rank_to_T[self.rank] = self.int_buffer
        self.comm.Allgather(self.rank_to_T[self.rank], self.rank_to_T)
        self.mycalc.kT = self.kTs[self.rank_to_T[self.rank]]
        return

    def run(self, nsteps, RXtrial_frequency, sample_frequency=verylargeint,
            print_frequency=verylargeint,
            observer=observer_base(), subdirs=True, save_obs=True):
        if subdirs:
            try:
                os.mkdir(str(self.rank))
            except FileExistsError:
                pass
            os.chdir(str(self.rank))
        self.accept_count = 0
        self.mycalc.energy = self.mycalc.model.energy(self.mycalc.config)
        test_observe = observer.observe(self.mycalc,open(os.devnull,"w"),lprint=False)
        if hasattr(test_observe,"__iter__"):
            obs_len = len(test_observe)
            obs = np.zeros([len(self.kTs), obs_len])
        if hasattr(test_observe,'__add__'):
            observe = True
        else:
            observe = False
        nsample = 0
        XCscheme = 0
        output = open("obs.dat", "a")
        for i in range(1,nsteps+1):
            self.mycalc.MCstep()
            if i%RXtrial_frequency == 0:
                self.Xtrial(XCscheme)
                XCscheme = (XCscheme+1)%2
            if i%sample_frequency == 0 and observe:
                obs_step = observer.observe(self.mycalc, output,i%print_frequency==0)
                obs[self.rank_to_T[self.rank]] += obs_step
                if save_obs:
                    self.obs_save.append(obs_step)
                    self.Trank_hist.append(self.rank_to_T[self.rank])
                    self.kT_hist.append(self.mycalc.kT)
                nsample += 1

        pickle.dump(self.mycalc.config, open("calc.pickle","wb"))
        if save_obs:
            if hasattr(self, "obs_save0"):
                obs_save_ = np.concatenate((self.obs_save0, np.array(self.obs_save)))
                Trank_hist_ = np.concatenate((self.Trank_hist0, np.array(self.Trank_hist)))
                kT_hist_ = np.concatenate((self.kT_hist0, np.array(self.kT_hist)))
            else:
                obs_save_ = np.array(self.obs_save)
                Trank_hist_ = np.array(self.Trank_hist)
                kT_hist_ = np.array(self.kT_hist)

            np.save(open("obs_save.npy","wb"), obs_save_, False)
            np.save(open("Trank_hist.npy", "wb"), Trank_hist_, False)
            np.save(open("kT_hist.npy", "wb"), kT_hist_, False)

        if subdirs: os.chdir("../")

        if self.rank == 0:
            pickle.dump(self.rank_to_T, open("rank_to_T.pickle","wb"))
            np.save(open("kTs.npy", "wb"), self.kTs, False)

        if nsample != 0:
            obs = np.array(obs)
            obs_buffer = np.empty(obs.shape)
            obs /= nsample
            self.comm.Allreduce(obs, obs_buffer, op=MPI.SUM)
            obs_list = []
            args_info = observer.obs_info(self.mycalc)
            for i in range(len(self.kTs)):
                obs_list.append(obs_decode(args_info,obs_buffer[i]))
            return obs_list


# -*- coding: utf-8 -*-
"""
Tests the reading of the file from the Google Sheets website.
"""
import pytest
import pandas as pd
from grfc.game import remote_handler as rh, RANGE_NAME

def test_fetch_round_1_data_from_google_sheets():
    round_1_spreadsheet = rh.spreadsheet_data(RANGE_NAME.format(1))
    values = round_1_spreadsheet.get('values', [])
    round_1 = pd.DataFrame(values[1:], columns=values[0])
    assert round_1.loc[0, 'Player 1'] == 'Elliot'

def test_fecth_invalid_round_from_google_sheets():
    no_round = rh.spreadsheet_data(RANGE_NAME.format(0))
    values = no_round.get('values', [])
    assert not values

def test_fetch_all_rounds_data_from_google_sheets():
    all_rounds = [rh.spreadsheet_data(RANGE_NAME.format(round)) for round in range(1, 19)]
    round_1_values = all_rounds[0].get('values', [])
    round_1 = pd.DataFrame(round_1_values[1:], columns=round_1_values[0])
    assert round_1.loc[0, 'Player 1'] == 'Elliot'

@pytest.mark.sst
def test_fetch_whole_tigers_spreadsheet():
    ranges = [f"'Round {round}'!A1:G29" for round in range(1, 19)]
    tigers = rh.spreadsheet_data(ranges)
    assert tigers.get('valueRanges', [])


import json
from http import HTTPStatus
from json import JSONDecodeError
from threading import Thread

from django.db.models import F
from django.http import JsonResponse
from django.urls import reverse
from django.views import View

from onequiz.operations.featureFlagOperations import featureFlagOperations, FeatureFlagType
from onequiz.operations.generalOperations import (
    QuestionAndResponse, QuizAttemptAutomaticMarking, QuizAttemptManualMarking
)
from quiz.models import (
    EssayResponse, MultipleChoiceResponse, QuizAttempt, Topic, TrueOrFalseResponse, Result, Response, Quiz
)

class TopicObjectApiEventVersion1Component(View):
    def get(self, *args, **kwargs):
        topics = Topic.objects.filter(**self.request.GET.dict()).annotate(
            _id=F('id'), _name=F('name'), _description=F('description')
        ).values('id', 'name', 'description')

        response = {
            "success": True,
            "data": {
                "topics": list(topics)
            }
        }
        return JsonResponse(response, status=HTTPStatus.OK)

class QuizAttemptObjectApiEventVersion1Component(View):

    def createResponseModels(self, quizAttempt, questionList):
        responseList = []
        essayResponseList = []
        trueOrFalseQuestionList = []
        multipleChoiceQuestionList = []

        for question in questionList:
            response = Response(question_id=question.id)
            responseList.append(response)

            if hasattr(question, 'essayQuestion'):
                essayResponseList.append(
                    EssayResponse(response=response, answer='')
                )
            elif hasattr(question, 'trueOrFalseQuestion'):
                trueOrFalseQuestionList.append(
                    TrueOrFalseResponse(response=response, trueSelected=None)
                )
            elif hasattr(question, 'multipleChoiceQuestion'):
                choiceList = question.multipleChoiceQuestion.choices['choices']
                for item in choiceList:
                    item['isChecked'] = False
                    del item['isCorrect']
                multipleChoiceQuestionList.append(
                    MultipleChoiceResponse(response=response, answers={'answers': choiceList})
                )
            else:
                print('Unknown question type.')
                continue

        Response.objects.bulk_create(responseList)
        EssayResponse.objects.bulk_create(essayResponseList)
        TrueOrFalseResponse.objects.bulk_create(trueOrFalseQuestionList)
        MultipleChoiceResponse.objects.bulk_create(multipleChoiceQuestionList)
        quizAttempt.responses.add(*responseList)
        return

    def post(self, *args, **kwargs):
        quizId = self.request.GET.get('quizId')
        existingQuizAttempt = QuizAttempt.objects.filter(
            quiz_id=quizId, user=self.request.user, status=QuizAttempt.Status.IN_PROGRESS
        )
        if existingQuizAttempt.exists():
            response = {
                "success": True,
                "message": "You already have an attempt that is in progress.",
                "redirectUrl": reverse('quiz:quiz-attempt-view', kwargs={'attemptId': existingQuizAttempt[0].id})
            }
            return JsonResponse(response, status=HTTPStatus.OK)

        quiz = Quiz.objects.prefetch_related(
            'questions__essayQuestion', 'questions__trueOrFalseQuestion', 'questions__multipleChoiceQuestion'
        ).get(id=quizId)
        newQuizAttempt = QuizAttempt.objects.create(user=self.request.user, quiz_id=quiz.id)
        questionList = quiz.getQuestions()

        self.createResponseModels(newQuizAttempt, questionList)
        newQuizAttempt.status = QuizAttempt.Status.IN_PROGRESS
        newQuizAttempt.save()

        response = {
            "success": True,
            "redirectUrl": reverse('quiz:quiz-attempt-view', kwargs={'attemptId': newQuizAttempt.id})
        }
        return JsonResponse(response, status=HTTPStatus.OK)

class QuizMarkingOccurrenceApiEventVersion1Component(View):

    def put(self, *args, **kwargs):
        quizAttempt = QuizAttempt.objects.select_related('quiz').prefetch_related(
            'responses__question',
        ).get(id=kwargs.get('id'))
        try:
            put = json.loads(self.request.body)
        except JSONDecodeError:
            put = json.loads(self.request.body.decode().replace('"', "'").replace("'", '"'))

        questionList = quizAttempt.quiz.getQuestions()
        responseList = quizAttempt.responses.all()
        awardedMarks = put.get('response', [])
        quizAttemptManualMarking = QuizAttemptManualMarking(quizAttempt, questionList, responseList, awardedMarks)

        if quizAttemptManualMarking.mark():
            resultObject = quizAttemptManualMarking.result
            resultObject.versionNo = Result.objects.filter(quizAttempt=quizAttempt).count() + 1
            quizAttempt.status = QuizAttempt.Status.MARKED
            resultObject.save()
            quizAttempt.save()

        response = {
            "success": True,
            "redirectUrl": reverse('quiz:quiz-attempt-result-view', kwargs={'attemptId': quizAttempt.id})
        }
        return JsonResponse(response, status=HTTPStatus.OK)

class QuestionResponseUpdateApiEventVersion1Component(View):

    def put(self, *args, **kwargs):
        if not featureFlagOperations.isEnabled(FeatureFlagType.SAVE_QUIZ_ATTEMPT_RESPONSE_AS_DRAFT):
            response = {
                "success": False,
                "message": f'Feature flag {FeatureFlagType.SAVE_QUIZ_ATTEMPT_RESPONSE_AS_DRAFT.value} not enabled.'
            }
            return JsonResponse(response, status=HTTPStatus.OK)

        try:
            put = json.loads(self.request.body)
        except JSONDecodeError:
            put = json.loads(self.request.body.decode().replace('"', "'").replace("'", '"'))

        qaid = put.get('quizAttemptId')
        qid = put.get('question').get('id')
        rid = put.get('response').get('id')

        quizAttempt = QuizAttempt.objects.select_related().prefetch_related('responses__question').get(id=qaid)
        responseList = quizAttempt.responses.all()
        responseInstance = next((r for r in responseList if r.question.id == qid and r.id == rid), None)

        if put.get('question').get('type') == 'EssayQuestion':
            responseInstance.essayResponse.answer = put.get('response').get('text')
            responseInstance.essayResponse.save()
        elif put.get('question').get('type') == 'TrueOrFalseQuestion':
            responseInstance.trueOrFalseResponse.trueSelected = put.get('response').get('selectedOption')
            responseInstance.trueOrFalseResponse.save()
        elif put.get('question').get('type') == 'MultipleChoiceQuestion':
            responseInstance.multipleChoiceResponse.answers['answers'] = put.get('response').get('choices')
            responseInstance.multipleChoiceResponse.save()

        response = {
            "success": True
        }
        return JsonResponse(response, status=HTTPStatus.OK)

class QuizAttemptQuestionsApiEventVersion1Component(View):

    def get(self, *args, **kwargs):
        quizAttempt = QuizAttempt.objects.prefetch_related(
            'responses__essayResponse', 'responses__trueOrFalseResponse', 'responses__multipleChoiceResponse',
            'responses__question',
        ).get(id=kwargs.get('id'))
        questionList = quizAttempt.quiz.getQuestions(quizAttempt.quiz.inRandomOrder)
        responseList = quizAttempt.responses.all()
        computedResponseList = QuestionAndResponse(questionList, responseList)

        response = {
            "success": True,
            "data": {
                "questions": computedResponseList.getResponse(),
                "quiz": {
                    "canEdit": quizAttempt.status in [QuizAttempt.Status.NOT_ATTEMPTED, QuizAttempt.Status.IN_PROGRESS]
                }
            }
        }
        return JsonResponse(response, status=HTTPStatus.OK)

    def put(self, *args, **kwargs):
        quizAttempt = QuizAttempt.objects.select_related('quiz').prefetch_related(
            'responses__essayResponse', 'responses__trueOrFalseResponse', 'responses__multipleChoiceResponse'
        ).get(id=kwargs.get('id'))

        try:
            put = json.loads(self.request.body)
        except JSONDecodeError:
            put = json.loads(self.request.body.decode().replace('"', "'").replace("'", '"'))

        userResponse = put.get('response', [])
        responseList = quizAttempt.responses.all()

        essayResponseObjects = []
        trueOrFalseResponseObjects = []
        multipleChoiceResponseObjects = []

        for response in userResponse:
            if response['type'] == 'EssayQuestion':
                existingResponseObject = next((o for o in responseList if o.id == response['response']['id']))
                if existingResponseObject is not None:
                    existingResponseObject.essayResponse.answer = response['response']['text']
                    essayResponseObjects.append(existingResponseObject.essayResponse)
            elif response['type'] == 'TrueOrFalseQuestion':
                existingResponseObject = next((o for o in responseList if o.id == response['response']['id']))
                if existingResponseObject is not None:
                    existingResponseObject.trueOrFalseResponse.trueSelected = response['response']['selectedOption']
                    trueOrFalseResponseObjects.append(existingResponseObject.trueOrFalseResponse)
            elif response['type'] == 'MultipleChoiceQuestion':
                existingResponseObject = next((o for o in responseList if o.id == response['response']['id']))
                if existingResponseObject is not None:
                    existingResponseObject.multipleChoiceResponse.answers['answers'] = response['response']['choices']
                    multipleChoiceResponseObjects.append(existingResponseObject.multipleChoiceResponse)
            else:
                continue

        EssayResponse.objects.bulk_update(essayResponseObjects, ['answer'])
        TrueOrFalseResponse.objects.bulk_update(trueOrFalseResponseObjects, ['trueSelected'])
        MultipleChoiceResponse.objects.bulk_update(multipleChoiceResponseObjects, ['answers'])

        # TODO: Optimize when automatic marking is enabled.
        quizAttemptAutomaticMarking = QuizAttemptAutomaticMarking(quizAttempt, quizAttempt.quiz.getQuestions(), responseList)
        if quizAttemptAutomaticMarking.mark():
            quizAttempt.status = QuizAttempt.Status.MARKED
        else:
            quizAttempt.status = QuizAttempt.Status.SUBMITTED

        quizAttempt.save()

        response = {
            "success": True,
        }
        return JsonResponse(response, status=HTTPStatus.OK)