#!/usr/bin/python3

"""
Module gérant l'interface graphique des fenêtres liées à la gestion des devoirs.
"""

## imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from autocompletion import *
from traitements.traitements import *
from dbManagement.dbManagement import BaseDeDonnées

## Classe pour la création des questions d'un devoir
class FenêtreQuestionsDevoir(object):
    """
    Classe gérant la construction d'une fenêtre popup pour la définition des questions /
    compétences associées à un devoir.

    Doit reçevoir un objet Devoir en paramètre.

    Ne gère pas l'attachement de plus de 10 compétences à chaque question.
    """
    maxCompétenceParQuestion = 10
    def __init__(self, devoir:Devoir, builder:Gtk.Builder, bdd:BaseDeDonnées, idDev:int):
        """
        Constructeur de la fenêtre.

        Utilise le patron disponible dans le .glade, donné par le Gtk.Builder, puis modifie
        les lignes en fonction des besoins du devoir considéré.
        """
        self.devoir_save = devoir
        self.fenêtre = builder.get_object("fenêtreQuestionsDevoir")
        self.vue = builder.get_object("fenêtreQuestionsDevoirAfficheur")
        self.vueModificateurs = builder.get_object("fenêtreQuestionsDevoirModificateurs")
        self.modèleModificateurs = builder.get_object("modèleModificateursDevoir")
        self.modèleCompétence = builder.get_object("modèleCompétence")
        self.saisieNoteMax = builder.get_object("fenêtreQuestionsDevoirSaisieNoteMax")
        self.saisieNvxComp = builder.get_object("fenêtreQuestionsDevoirSaisieNvxComp")
        self.fenêtre.set_title("Mise en place des questions du " + devoir.get_enTêteDevoir())
        builder.get_object("fenêtreQuestionsDevoirAppliquer").connect("clicked",self.sauvegarderCompétences)
        # Mise en place du modèle - doit être cohérent avec FenêtreQuestionsDevoir.maxCompétenceParQuestion
        self.modèle = Gtk.ListStore(str, \
                                    str,int,str,int,str,int,str,int,str,int,str,int,str,int,str,int,str,int,str,int)
        # Mise en place de la vue principale
        while self.vue.get_n_columns() > 0:
            self.vue.remove_column(self.vue.get_column(0))
        self.vue.set_model(self.modèle)
        rdNuméro = Gtk.CellRendererText()
        rdNuméro.set_property("editable",True)
        rdNuméro.connect("edited", self.celluleÉditée, (self.modèle,0,str))
        self.vue.append_column(Gtk.TreeViewColumn("N°",rdNuméro,text=0))
        for i in range(FenêtreQuestionsDevoir.maxCompétenceParQuestion):
            col = Gtk.TreeViewColumn("Compétence {}".format(i+1))
            completion = Gtk.EntryCompletion()
            rdComp = CellRendererAutoComplete(completion)
            rdCoef = Gtk.CellRendererText()
            completion.set_model(self.modèleCompétence)
            completion.pack_start(rdComp, True)
            completion.add_attribute(rdComp, "text", 0)
            completion.props.text_column = 0
            completion.set_match_func(match_anywhere, None)
            rdComp.set_property("editable",True)
            rdCoef.set_property("editable",True)
            rdComp.connect("edited", self.celluleÉditée, (self.modèle,2*i+1,str))
            rdCoef.connect("edited", self.celluleÉditée, (self.modèle,2*i+2,int))
            col.pack_start(rdComp,True)
            col.pack_start(rdCoef,False)
            col.add_attribute(rdComp, "text", 2*i+1)
            col.add_attribute(rdCoef, "text", 2*i+2)
            self.vue.append_column(col)
        self.vue.append_column(Gtk.TreeViewColumn("",Gtk.CellRendererText()))
        self.vue.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
        # Mise en place de la vue des modificateurs
        while self.vueModificateurs.get_n_columns() > 0:
            self.vueModificateurs.remove_column(self.vueModificateurs.get_column(0))
        typesModifs = Gtk.ListStore(str)
        [ typesModifs.append([typ]) for typ in devoir.get_listeCatégoriesModificateurs() ]
        col1, col2, col3 = Gtk.TreeViewColumn("Type de modificateur"), \
                           Gtk.TreeViewColumn("Nom"), Gtk.TreeViewColumn("Valeur")
        completion = Gtk.EntryCompletion()
        rdCatMod, rdNomMod, rdValMod = CellRendererAutoComplete(completion), \
                                       Gtk.CellRendererText(), Gtk.CellRendererText()
        completion.set_model(typesModifs)
        completion.pack_start(rdCatMod, True)
        completion.add_attribute(rdCatMod,"text",0)
        completion.props.text_column = 0
        completion.set_match_func(match_anywhere, None)
        rdCatMod.set_property("editable",True)
        rdNomMod.set_property("editable",True)
        rdValMod.set_property("editable",True)
        rdCatMod.connect("edited", self.celluleÉditée, (self.modèleModificateurs,0,str))
        rdNomMod.connect("edited", self.celluleÉditée, (self.modèleModificateurs,1,str))
        rdValMod.connect("edited", self.celluleÉditée, (self.modèleModificateurs,2,float))
        col1.pack_start(rdCatMod,True)
        col2.pack_start(rdNomMod,True)
        col3.pack_start(rdValMod,True)
        col1.add_attribute(rdCatMod, "text", 0)
        col2.add_attribute(rdNomMod, "text", 1)
        col3.add_attribute(rdValMod, "text", 2)
        col2.set_expand(True)
        col3.set_cell_data_func(rdValMod, \
                                lambda col, cell, model, iter, unused:
                                cell.set_property("text", "{:.2f}".format(model.get(iter, 2)[0])))
        self.vueModificateurs.append_column(col1)
        self.vueModificateurs.append_column(col2)
        self.vueModificateurs.append_column(col3)
        self.vue.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
        self.ajouterLigne(self.modèleModificateurs)
        # Initialisation des données
        self.bdd = bdd
        self.idDev = idDev
        self.màjModèle()
        self.gestionAffichage()
        # Lancement
        self.fenêtre.show_all()
        réponse = self.fenêtre.run()
        if réponse == Gtk.ResponseType.OK:
            self.sauvegarderCompétences(None)
        self.fenêtre.hide()

    def ajouterLigne(self,modèle) -> None:
        """
        Fonction interne pour ajouter une ligne vide au modèle, servant à créer une nouvelle question.
        """
        if modèle == self.modèle:
            nom = ""
            cp_coeff = ["",0]*FenêtreQuestionsDevoir.maxCompétenceParQuestion
            self.modèle.append([nom]+cp_coeff)
        elif modèle == self.modèleModificateurs:
            self.modèleModificateurs.append(["","",0])

    def màjModèle(self) -> None:
        """
        Fonction interne qui reset les modèles à partir des données du self.devoir_save
        """
        self.modèle.clear()
        for q in self.devoir_save.get_listeQuestionsModèle(FenêtreQuestionsDevoir.maxCompétenceParQuestion):
            self.modèle.append(q)
        self.ajouterLigne(self.modèle)
        self.modèleModificateurs.clear()
        for m in self.devoir_save.get_listeModificateursModèle():
            self.modèleModificateurs.append(m)
        self.ajouterLigne(self.modèleModificateurs)
        self.saisieNoteMax.set_text(str(self.devoir_save.get_noteMax()))
        self.saisieNvxComp.set_text(str(self.devoir_save.get_niveauxAcquisition()))

    def celluleÉditée(self, cellule, path, text, data):
        """
        Callback pour la mise à jour du modèle quand une case est éditée.
        """
        modèle,num,typ = data
        text = typ(text)
        modèle[path][num] = text
        if modèle[path][0] == "":
            modèle.remove(modèle.get_iter(path))
        if int(path) == len(modèle)-1:  # gestion de l'ajout de nouvelle ligne
            self.ajouterLigne(modèle)
        self.gestionAffichage()

    def gestionAffichage(self):
        """
        Fonction ayant pour rôle de définir les colonnes visibles ou non.
        """
        listeCompétences = [ [k for k in a[1:] if type(k) == str and k != ""] for a in self.modèle ]
        nbCols = max([ len(a) for a in listeCompétences ]) + 2
        for i in range(1,nbCols):
            self.vue.get_column(i).set_visible(True)
        for i in range(nbCols,FenêtreQuestionsDevoir.maxCompétenceParQuestion+1):
            self.vue.get_column(i).set_visible(False)

    def sauvegarderCompétences(self,dummy):
        """
        Callback utilisé pour mettre à jour l'objet Devoir sous-jacent à partir du modèle.
        """
        listeModèle = [ ligne[:] for ligne in self.modèle ]
        listeModèle.pop()  # on enlève la ligne vide
        listeCompétences = [ ligne[0] for ligne in self.modèleCompétence ]
        self.devoir_save.set_questionsDepuisModèle(listeModèle, listeCompétences)
        listeModèle = [ ligne[:] for ligne in self.modèleModificateurs ]
        listeModèle.pop()  # on enlève la ligne vide
        self.devoir_save.set_modificateursDepuisModèle(listeModèle)
        self.devoir_save.set_noteMax(self.saisieNoteMax.get_text())
        self.devoir_save.set_niveauxAcquisition(self.saisieNvxComp.get_text())
        # Gestion de la BDD
        self.bdd.sauverDevoir(self.devoir_save.get_évaluationBDD())
        self.màjModèle()

## Classe pour la saisie des évaluations lors d'un devoir
class FenêtreÉvaluationDevoir(object):
    """
    Classe gérant la construction d'une fenêtre popup pour l'évaluation d'un devoir
    par compétences.

    Doit reçevoir un objet Devoir en paramètre.
    """
    colÉtNo, colÉtNom, colÉtPrésence, colÉtÉvalué = 0,1,2,3
    colQuest, colComp, colCoef, colÉval = 0,1,2,3
    premièreOuverture = True
    handler_id_sélecteurÉtudiant = None
    def __init__(self, devoir:Devoir, builder:Gtk.Builder, bdd:BaseDeDonnées, idDev:int):
        """
        Constructeur de la fenêtre.

        Utilise le patron disponible dans le .glade, donné par le Gtk.Builder, puis modifie
        les lignes en fonction des besoins du devoir considéré.
        """
        # Récupération de la fenêtre et des objets
        self.fenêtre = builder.get_object("fenêtreÉvaluationDevoir")
        self.modèle = builder.get_object("fenêtreÉvaluationDevoirModèle")
        self.vue = builder.get_object("fenêtreÉvaluationDevoirTreeView")
        self.sélecteurÉtudiant = builder.get_object("fenêtreÉvaluationDevoirSélecteurÉtudiant")
        self.switchPrésence = builder.get_object("fenêtreÉvaluationDevoirSwitchPrésence")
        self.modèlePointsFixes = builder.get_object("fenêtreÉvaluationDevoirModèlePointsFixes")
        self.modèleModificateurs = builder.get_object("fenêtreÉvaluationDevoirModèleModificateurs")
        self.vuePointsFixes = {'label':builder.get_object("fenêtreÉvaluationDevoirLabelPointsFixes"),
                               'tv':builder.get_object("fenêtreÉvaluationDevoirTreeViewPointsFixes"),
                               'rd':builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes")}
        self.vueModificateurs = {'label':builder.get_object("fenêtreÉvaluationDevoirLabelModifs"),
                                 'tv':builder.get_object("fenêtreÉvaluationDevoirTreeviewModifs"),
                                 'rd':builder.get_object("fenêtreÉvaluationDevoirRdModifs")}
        if FenêtreÉvaluationDevoir.premièreOuverture:
            renderer_étudiant = Gtk.CellRendererText()
            renderer_étudiant.set_property("foreground","grey")
            self.sélecteurÉtudiant.pack_start(renderer_étudiant, True)
            self.sélecteurÉtudiant.add_attribute(renderer_étudiant, "text", FenêtreÉvaluationDevoir.colÉtNom)
            self.sélecteurÉtudiant.add_attribute(renderer_étudiant,'foreground-set',\
                                                 FenêtreÉvaluationDevoir.colÉtÉvalué)
            rdr = builder.get_object("fenêtreÉvaluationDevoirTreeViewColonneNomRenderer")
            builder.get_object("fenêtreÉvaluationDevoirTreeViewColonneNom").add_attribute(rdr,'visible',4)
            col = builder.get_object("fenêtreÉvaluationDevoirColModifsVal")
            col.set_cell_data_func(builder.get_object("fenêtreÉvaluationDevoirColModifsValRd"), \
                                   lambda col, cell, model, iter, unused:
                                   cell.set_property("text", "{:.2f}".format(model.get(iter, 1)[0])))
            col = builder.get_object("fenêtreÉvaluationDevoirColPointsFixes")
            col.set_cell_data_func(builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes"), \
                                   lambda col, cell, model, iter, unused:
                                   cell.set_property("text", "{:.2f}".format(model.get(iter, 2)[0])))
            col = builder.get_object("fenêtreÉvaluationDevoirColPointsFixes1")
            col.set_cell_data_func(builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes1"), \
                                   lambda col, cell, model, iter, unused:
                                   cell.set_property("text", "{:.2f}".format(model.get(iter, 1)[0])))
            FenêtreÉvaluationDevoir.premièreOuverture = False
        # Préparation des attributs autres
        self.fenêtre.set_title("Évaluation du " + devoir.get_enTêteDevoir())
        self.devoir_save = devoir
        self.étudiantActif = 0
        self.bdd = bdd
        self.idDev = idDev
        # Sélection des étudiants
        if FenêtreÉvaluationDevoir.handler_id_sélecteurÉtudiant is not None:
            self.sélecteurÉtudiant.disconnect(FenêtreÉvaluationDevoir.handler_id_sélecteurÉtudiant)
        self.modèleÉtudiants = Gtk.ListStore(int,str,bool,bool)
        for ét in devoir.get_listeÉtudiantsModèle():
            self.modèleÉtudiants.append(ét)
        self.sélecteurÉtudiant.set_model(self.modèleÉtudiants)
        self.sélecteurÉtudiant.set_active(self.étudiantActif)
        self.changeÉtudiant(self.sélecteurÉtudiant,False)
        # Autres infos
        texte_nvx = "Niveaux d'acquisition : {}".format(devoir.get_niveauxAcquisition())
        builder.get_object("fenêtreÉvaluationDevoirLabelNvxComp").set_text(texte_nvx)
        builder.get_object("fenêtreÉvaluationDevoirTreeViewColonneÉval").\
            set_title("Éval (/{})".format(devoir.get_niveauxAcquisition()-1))
        # Connection des signaux
        builder.get_object("fenêtreÉvaluationDevoirÉditeurÉval").connect("edited", self.évaluationÉditée, \
                                                                         (self.modèle,int))
        FenêtreÉvaluationDevoir.handler_id_sélecteurÉtudiant = self.sélecteurÉtudiant.\
                                                               connect("changed", self.changeÉtudiant)
        builder.get_object("fenêtreÉvaluationDevoirBoutonAppliquer").connect("clicked", self.sauvegarderÉvaluation)
        builder.get_object("fenêtreÉvaluationDevoirÉditeurÉval").connect("edited", self.évaluationÉditée,
                                                                         (self.modèle,int))
        self.switchPrésence.connect("state_set", self.switchPrésenceÉtudiant)
        builder.get_object("fenêtreÉvaluationDevoirColModifsÉval").connect("edited",self.évaluationÉditée, \
                                                                           (self.modèleModificateurs,int))
        builder.get_object("fenêtreÉvaluationDevoirRdPointsFixes").connect("edited",self.évaluationÉditée, \
                                                                           (self.modèlePointsFixes,float))
        # Lancement
        self.fenêtre.show()
        réponse = self.fenêtre.run()
        if réponse == Gtk.ResponseType.OK:
            self.sauvegarderÉvaluation(None)
        self.fenêtre.hide()

    def évaluationÉditée(self, cellule, path, text, data):
        """
        Callback pour la mise à jour du modèle quand une case est éditée.
        """
        modèle,typ = data
        try:
            val = typ(text)
            if modèle == self.modèle and val < self.devoir_save.get_niveauxAcquisition() and val > -2:
                modèle[path][FenêtreÉvaluationDevoir.colÉval] = val
            elif modèle == self.modèlePointsFixes and val <= modèle[path][1]:
                modèle[path][2] = val
            elif modèle == self.modèleModificateurs:
                modèle[path][2] = val
        except ValueError:  # cas d'un non-entier proposé
            pass

    def switchPrésenceÉtudiant(self, switch:Gtk.Switch, data:object) -> None:
        """
        Callback pour la modification du statut présent ou non de l'étudiant.
        """
        self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence] = switch.get_active()

    def changeÉtudiant(self, sélecteur:Gtk.ComboBox, sauvegarder:bool=True) -> None:
        """
        Callback pour le changement d'étudiant.
        """
        if sauvegarder:
            self.sauvegarderÉvaluation()
        self.étudiantActif = self.sélecteurÉtudiant.get_active()
        self.modèle.clear()
        self.modèlePointsFixes.clear()
        self.modèleModificateurs.clear()
        évaluation = self.devoir_save.get_évaluationÉtudiantModèle(self.étudiantActif)
        self.switchPrésence.set_active(évaluation[1])
        self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence] = évaluation[1]
        for row in évaluation[0]:
            if row[0] == "points fixes":
                self.modèlePointsFixes.append(row[1:])
            elif row[0] == "pourcentage":
                self.modèleModificateurs.append(row[1:])
            else:
                self.modèle.append(row + [True])
                if len(self.modèle) > 1 and self.modèle[-1][0] == self.modèle[-2][0]:
                    self.modèle[-1][-1] = False
        if len(self.modèlePointsFixes) == 0:
            self.vuePointsFixes['label'].set_visible(False)
            self.vuePointsFixes['tv'].set_visible(False)
        else:
            self.vuePointsFixes['label'].set_visible(True)
            self.vuePointsFixes['tv'].set_visible(True)
        if len(self.modèleModificateurs) == 0:
            self.vueModificateurs['label'].set_visible(False)
            self.vueModificateurs['tv'].set_visible(False)
        else:
            self.vueModificateurs['label'].set_visible(True)
            self.vueModificateurs['tv'].set_visible(True)

    def sauvegarderÉvaluation(self, data:object=None):
        """
        Callback pour sauvegarder l'évaluation de l'étudiant.
        """
        liste = [ ["points fixes"]+row[:] for row in self.modèlePointsFixes ] + \
                [ ["pourcentage"] +row[:] for row in self.modèleModificateurs] + \
                [ row[:] for row in self.modèle ]
        présence = self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence]
        self.devoir_save.set_évaluationÉtudiantModèle(self.étudiantActif, liste, présence)
        uneÉval = (np.array([ row[FenêtreÉvaluationDevoir.colÉval] for row in self.modèle ]) > -1).any()
        présence = self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtPrésence]
        self.modèleÉtudiants[self.étudiantActif][FenêtreÉvaluationDevoir.colÉtÉvalué] = uneÉval or not présence
        self.bdd.sauverÉvaluationUnÉtudiant(self.devoir_save.get_évaluationBDDUnÉtudiant(self.étudiantActif))


import random

for i in range(3):
    print(random.random())

for i in range(3):
    print(random.randint(10,20))

members = ['John','Mary','Bob','Josh']

leader = random.choice(members)

print(leader)

class Dice:
    def roll(self):
        tupl = []
        first = random.randint(1,6)
        second = random.randint(1,6)
        return first, second

snakieDie = Dice()

print(snakieDie.roll())


""" Defines the robot class and some actions the robot can perform and the main real-time control loop.
"""
import time
import threading
import logging
import math
import numpy as np
import sm_kinematics

log = logging.getLogger(__name__)

from enum import Enum

import config
# import joints
import pca9685_psd
import balance
import footpath

# FIXME
# import imu
# imu = imu.IMU()

# We always count and order the legs this way, left to right and front to rear
class LegId(Enum):
    LF = 0  # Left Front is first and number 0
    RF = 1
    LR = 2
    RR = 3  # Right Rear is last and number 3

# These are the things the robot can do.  So the robot can be in the
# "walk state" or the "neutral_stand state" or one of the others
class BehaviorState(Enum):
    INITIAL_POWERUP = 0     # Legs are in unknown position
    NEUTRAL_STAND   = 1     # Standing straight at medium height and weight equal on all feet
    WALK            = 2     # Actively walking, although velocity could be zero
    SIT             = 3     # Sit posture, with body tilted forward end upward

# These are sub-states. Each of the above states can have one of these sub-states.
class BehaviorSubState(Enum):
    STARTING     = 0       # Transition to current Behavior State
    CONTINUING   = 1
    TERMINATING  = 2       # Behavior is ending

init_time = time.time()
prior_state = BehaviorState.INITIAL_POWERUP
robot_substate = BehaviorSubState.STARTING

# PARAMETERS ##########################
neutral_body_height = 0.200
neutral_stance_width = 0.160
neutral_stance_length = 0.265

# Inter Task GLOBAL VARIABLES ####################
# These are used for inter-task communication.  The real-time control task
# reads them and the GUI or other controller task sets them.
# Always use the lock to access these.
gbl_run_control_loop = False

gbl_robot_state    = BehaviorState.INITIAL_POWERUP

# The next three variables are equivalent to the ROS "twist" message.
gbl_velocity_x = 0.0
gbl_velocity_y = 0.0
gbl_rot_rate_z = 0.0

gbl_stance_width   = neutral_stance_width
gbl_stance_length  = neutral_stance_length
gbl_body_center_up = neutral_body_height

gbl_body_center_left    = 0.0
gbl_body_center_forward = 0.0

gbl_body_angle_roll  = 0.0
gbl_body_angle_pitch = 0.0
gbl_body_angle_yaw   = 0.0

# Time to move from previous state to a new static pose such as "sit" or "stand"
# This should be set to something reasonable before changing the state to one of the static pose states
gbl_static_transition_time = 0.0

gbl_lock = threading.Lock()
## END GLOBALS #################

def limit_check_lp(Lp, check_id=''):
    """Perform a diagnostic error check on Lp array -- remove this in production code"""
    if abs(np.amax(Lp[:, :3])) > 0.300:
        # ERROR something is wrong, the legs are not over 300 mm long
        log.error('Lp array limit check failed')
        print(check_id, Lp)
    return

def neutral_stance_Lp() -> np.array:
    """Function to return a reasonable neutral stance on flat ground"""

    global gbl_stance_width
    global gbl_body_center_up
    global gbl_run_control_loop

    # Units for these are Meters.  Change the constants as opion about
    # what is best evolves.
    bias = -0.065

    if gbl_lock.acquire(blocking=True, timeout=0.5):
        stance_width  = gbl_stance_width
        stance_length = gbl_stance_length
        vertical      = gbl_body_center_up
        gbl_lock.release()
    else:
        log.warning('neutral_stance_Lp() failed to acquire lock')

    stance_halfwidth  = stance_width  / 2.0
    stance_halflength = stance_length / 2.0

    # Distances of each foot from body center
    #                   fore/aft               height      left/right
    Lp = np.array([[stance_halflength + bias, -vertical, stance_halfwidth, 1],  # LF
                   [stance_halflength + bias, -vertical, -stance_halfwidth, 1],  # RF
                   [-stance_halflength + bias, -vertical, stance_halfwidth, 1],  # LR
                   [-stance_halflength + bias, -vertical, -stance_halfwidth, 1]])  # RR

    limit_check_lp(Lp, check_id='get_neural_stance_Lp')
    return Lp

tick_start = 0.0
lp_neutral = neutral_stance_Lp()
lp = lp_neutral.copy()

velocity_x = 0.0
velocity_y = 0.0
rot_rate_z = 0.0
fp = footpath.Footpath()
body_center_up = 0.
transition_time = 0.

def control_loop():
    """
    Realtime control loop for robot base controller.

    This function should be called as a task.  After some setup, a loop is
    entered that runs many times per second.  The loop will,
      1) read shared variables, after obtaining lock
      2) compute the desired location in (x,y,z), body relative for each foot
      3) perform inverse kinetics to find the joint angles for each leg
      4) update the servo motors to move the joints to the desired angle
    The loop continues to run until gbl_run_control_loop is set to False.

    Returns: none.

    """

    ### GLOBALS used for inter task communication
    global gbl_lock
    global gbl_run_control_loop

    global gbl_robot_state

    global gbl_body_center_up
    global gbl_body_center_left
    global gbl_body_center_forward

    global gbl_body_angle_roll
    global gbl_body_angle_pitch
    global gbl_body_angle_yaw

    global prior_state
    global robot_substate

    global velocity_x
    global velocity_y
    global rot_rate_z
    global tick_start
    global body_center_up

    global gbl_static_transition_time
    global transition_time

    ### Channel number on PCA9685 for each angle
    servo_channel = np.array(config.servo_channel_assignment,
                             dtype=np.uint8)
    # Class to abstract real and simulated servo hardware
    # servos = joints.ServoShim()
    s = pca9685_psd.Servo()

    # smk (Spot Micro Kinematic) object
    smk = sm_kinematics.Kinematic()

    # Heartbeat will blink a LED.
    heartbeat_time = 0.0

    loop_period = 1.0 / config.rt_loop_freq
    run_control_loop = True  # Set True in case acquire lock fails.
    while True:
        tick_start = time.time()

        # copy global parameters
        if gbl_lock.acquire(blocking=True, timeout=loop_period / 2.0):
            run_control_loop = gbl_run_control_loop

            robot_state = gbl_robot_state

            velocity_x = gbl_velocity_x
            velocity_y = gbl_velocity_y
            rot_rate_z = gbl_rot_rate_z

            body_center_up      = gbl_body_center_up
            body_center_left    = gbl_body_center_left
            body_center_forward = gbl_body_center_forward

            body_angle_roll  = gbl_body_angle_roll
            body_angle_pitch = gbl_body_angle_pitch
            body_angle_yaw   = gbl_body_angle_yaw

            transition_time = gbl_static_transition_time

            gbl_lock.release()
        else:
            log.warning('control_loop() failed to acquire lock {0}'.format(tick_start))
            print('control_loop() failed to acquire lock {0}'.format(tick_start)) #TODO

        # Check if this thread should continue running
        if not run_control_loop:
            log.info('control loop terminating at {0}'.format(tick_start))
            print('control loop terminating at {0}'.format(tick_start)) #TODO
            break

        # Heartbeat -- show the world this thread is alive
        if tick_start > heartbeat_time:
            #FIXME need to blink a real LED
            log.debug('heartbeat at {0}'.format(tick_start))
            print('heartbeat at {0}'.format(tick_start)) #TODO remove
            heartbeat_time = tick_start + config.rt_heartbeat_period

        # Did the robot state just change?  If so then the sub state has to be "STARTING"
        if robot_state != prior_state:
            robot_substate = BehaviorSubState.STARTING
            prior_state = robot_state

        if robot_state == BehaviorState.INITIAL_POWERUP:
            # In this state, there is nothing to do.
            continue

        elif robot_state == BehaviorState.NEUTRAL_STAND:
            control_stand()
        elif robot_state == BehaviorState.WALK:
            control_walk()
        elif robot_state == BehaviorState.SIT:
            control_sit()
        else:
            log.error('Invalid State, resetting')
            robot_state = BehaviorState.INITIAL_POWERUP

        #FIXME -- add in any body position and angle needed for things like balance
        # roll_correction, pitch_correction = balance.rotate_to_level(imu.get_acceleration())
        roll_correction, pitch_correction = 0.0, 0.0

        # Find the joint angles to move the foot to the required position
        body_angles = (body_angle_roll + roll_correction,
                       body_angle_yaw,
                       body_angle_pitch + pitch_correction)
        body_center = (body_center_forward,
                       body_center_up-neutral_body_height,
                       body_center_left)

        joint_angles = smk.calcIK(lp, body_angles, body_center)

        # Set the robots joints to the angles computed above.
        # Note that the function below will check if the motors are enabled or if
        # we are running in simulation only.  So maybe the angles only go to a log
        # file, or they could move the physical robot.
        """
        for leg_idx, leg in enumerate(joint_angles):
            for theta_idx, theta in enumerate(leg):
                servos.set_angle(servo_channel[leg_idx, theta_idx], theta)"""

        # Turn (4, 3) array into a (16,) array.
        # First make it a (4, 4) and then flatten it
        joint_angles_flattened = np.append(joint_angles, [[0.0], [0.0], [0.0], [0.0]], 1).reshape(-1)
        s.move_16_radian(joint_angles_flattened)

        tick_elapsed = time.time() - tick_start
        sleep_time = loop_period - tick_elapsed

        if sleep_time < 0.001:
            log.warning('loop overrun, elapsed = {0}'.format(tick_elapsed))
        else:
            assert 0.001 < sleep_time < loop_period, 'invalid sleep_time {0}'.format(sleep_time)
            time.sleep(sleep_time)

    # this task ends
    log.debug('control loop hits return')
    return

stand_start_time = 0.0
lp_zero = np.array([[0., 0., 0., 0.],
                    [0., 0., 0., 0.],
                    [0., 0., 0., 0.],
                    [0., 0., 0., 0.]])

# Table specifies the phase when each foot begins to move and when it finishes
# phase the fraction of the transition_time that has past since stand_start_time.
# FIXME This table needs to be computed based on distance each foot is to be moved.
#
#             Start  End
leg_phase = [(0.00, 0.20),  # leg 0 LF
             (0.50, 0.70),  # leg 1 RF
             (0.25, 0.45),  # leg 2 LR
             (0.75, 0.95)]  # leg 4 RR

def control_stand() -> None:

    global gbl_lock

    global robot_substate
    global stand_start_time
    global lp_zero
    global lp_neutral

    global transition_time
    global lp

    if robot_substate == BehaviorSubState.STARTING:

        # Substate is "starting" so we run the initialization code then set state to
        # "continueing"
        stand_start_time = time.time()

        lp_zero = lp.copy()   # save current feet location
        lp_neutral = neutral_stance_Lp()
        robot_substate = BehaviorSubState.CONTINUING

    if robot_substate == BehaviorSubState.CONTINUING:

        phase = (time.time() - stand_start_time) / transition_time
        if phase < 1.0:
            lp =  (phase * lp_zero) +  ((1.0 - phase) * lp_neutral)

        else:
            lp = lp_neutral.copy()

    else:
        log.error('Invalid Substate')
    return

step_start = 0.0
#                     LF     RF     LR     RR
leg_relative_phase = [0.00, -0.50, -0.50, -0.00]
static_step_air_fraction = 0.0
last_velocity = (0., 0., 0.)
step_period = 0.001 # 0.001 is a hack to avid potential for divide by zero error

def control_walk():

    global gbl_lock
    global robot_substate
    global step_start
    global tick_start
    global leg_relative_phase
    global static_step_air_fraction
    global body_center_up
    global last_velocity
    global step_period

    if robot_substate == BehaviorSubState.STARTING:
        step_start = tick_start
        robot_substate = BehaviorSubState.CONTINUING

    if robot_substate == BehaviorSubState.CONTINUING:

        # if the robot is not moving, there is nothing to do...
        if (abs(velocity_x) + abs(velocity_y)) < 0.002:
            return

        # Only call fp.set_velocity on change
        if last_velocity != (velocity_x, velocity_y, rot_rate_z):
            step_period = fp.set_velocity(velocity_x, velocity_y, rot_rate_z)
            last_velocity = (velocity_x, velocity_y, rot_rate_z)

        # get the current step phase, handle wrap-around.  Phase can never
        # be greater than 1.0
        assert step_period > 0.01  # verify a sane value
        step_phase = (tick_start - step_start) / step_period
        if step_phase > 1.0:
            (step_phase, _) = math.modf(step_phase)
            step_start = tick_start - (step_period * step_phase)

        assert 0.0 <= step_phase <= 1.0, \
            'invalid Step Phase {0}, {1}, {2}, {3}'. \
                format(step_phase, tick_start, step_start, step_period)

        num_feet_on_ground = 0
        for leg_id in LegId:
            leg_abs_phase = step_phase + leg_relative_phase[leg_id.value]

            if leg_abs_phase < 0:
                leg_abs_phase = 1.0 + leg_abs_phase
            assert 0.0 <= leg_abs_phase <= 1.0

            fx, fy, fz = fp.xyz(leg_abs_phase)
            x_neutral = lp_neutral[leg_id.value, 0]
            y_neutral = lp_neutral[leg_id.value, 2]
            lp[leg_id.value, :3] = [fx + x_neutral,
                                    fz - body_center_up,
                                    fy + y_neutral]
            if fz == 0.0:
                num_feet_on_ground += 1

        # Check if all four feet are on the ground, if so then we can
        # update any of the step parameters
        if num_feet_on_ground == 4:
            # Now is the time, with all four feet down
            # to adjust the step parameters.
            step_period = fp.set_velocity(velocity_x, velocity_y, rot_rate_z)

def control_sit():
    pass

class Robot:
    """
    This class provides basic controls for the robot base controller.

    The Robot class provides a set of functions that return immediately, that are
    use to control the robot's basic motion and behaviors such as walk, sit, or
    stand.  Creating a Robot object will start a task initialize the real-time
    control loop.  The Robot class then has methods to create movements and to
    get status.
    """

    def __init__(self):
        """Create a Robot object and a task to control it."""

        self.lock_timeout = 0.5
        self.__start_control_loop_thread()

        return

    def kill(self):
        """Quickly stops the realtime control task."""

        global gbl_lock
        global gbl_run_control_loop
        global gbl_robot_state

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_run_control_loop = False
            gbl_robot_state = BehaviorState.INITIAL_POWERUP
            gbl_lock.release()
        else:
            log.error('kill() failed to acquire lock')
        return

    def set_velocity(self, vel_x: float = 0.0, vel_y: float = 0.0, rot_z: float = 0.0):
        """Sets the robot's velocity and rate of turn over ground."""

        global gbl_lock
        global gbl_velocity_x
        global gbl_velocity_y
        global gbl_rot_rate_z

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_velocity_x = vel_x
            gbl_velocity_y = vel_y
            gbl_rot_rate_z = rot_z
            gbl_lock.release()
        else:
            log.error('set_velocity() failed to acquire lock')
        return

    def walk(self, vel_x: float = 0.0, vel_y: float = 0.0, rot_z: float = 0.0):
        """Cause robot to walk."""

        global gbl_lock
        global gbl_robot_state

        self.set_velocity(vel_x, vel_y, rot_z)

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_robot_state = BehaviorState.WALK
            gbl_lock.release()
        else:
            log.error('walk() failed to acquire lock')
        return

    def stand(self, transition: float = 1.0):
        """Cause robot to stand in a neutral position."""

        global gbl_lock
        global gbl_robot_state
        global gbl_static_transition_time

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_static_transition_time = transition
            gbl_robot_state    = BehaviorState.NEUTRAL_STAND
            gbl_lock.release()
        else:
            log.error('stand() failed to acquire lock')
        return

    def sit(self, transition_time: float = 0.0):
        """Cause robot to sit."""

        global gbl_lock
        global gbl_robot_state

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_robot_state    = BehaviorState.SIT
            gbl_lock.release()
        else:
            log.error('failed to acquire lock')
        return

    def set_body_angles(self, body_angle: (float, float, float) = (0.0, 0.0, 0.0)):
        """Set body rotations with respect to neutral position."""

        global gbl_lock

        global gbl_body_angle_roll
        global gbl_body_angle_pitch
        global gbl_body_angle_yaw

        limit = math.radians(21.0)  # limit for all angles

        roll = body_angle[0]
        assert -limit <= roll <= limit, f"roll = {roll}"

        pitch = body_angle[1]
        assert -limit <= pitch <= limit, f"pitch = {pitch}"

        yaw = body_angle[2]
        assert -limit <= yaw <= limit, f"yaw = {yaw}"

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):

            gbl_body_angle_roll = roll
            gbl_body_angle_pitch = pitch
            gbl_body_angle_yaw = yaw

            gbl_lock.release()
        else:
            log.error('failed to acquire lock')

    def set_body_center(self, body_point: (float, float, float)):
        """Set body translations with respect to neutral position."""

        global gbl_lock

        global gbl_body_center_up
        global gbl_body_center_left
        global gbl_body_center_forward

        forward = body_point[0]
        assert -0.100 <= forward <= 0.100

        left = body_point[1]
        assert -0.100 <= left <= 0.100

        up = body_point[2]
        assert 0.120 <= up <= 0.240

        log.debug('acquiring the lock...')
        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):

            gbl_body_center_forward = forward
            gbl_body_center_left = left
            gbl_body_center_up = up

            gbl_lock.release()
        else:
            log.error('failed to acquire lock')

    def set_stance_width(self, stance_width_delta: float):
        """Set the distance between the left and right feet

        Parms:
            stance_width_delta  Reduction from nominal stance in meters
        """

        global gbl_lock
        global gbl_stance_width

        assert -0.050 < stance_width_delta < 0.050

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_stance_width = neutral_stance_width + stance_width_delta
            gbl_lock.release()
        else:
            log.error('failed to acquire lock')
        return

    def set_stance_length(self, stance_length: float):
        """Set the distance between the front and rear feet."""

        global gbl_lock
        global gbl_stance_length

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            gbl_stance_length = stance_length
            gbl_lock.release()
        else:
            log.error('failed to acquire lock')
        return

    def __start_control_loop_thread(self):
        """Private method to setup and run the real time control loop."""
        global gbl_lock
        global gbl_run_control_loop
        global gbl_robot_state
        global gbl_robot_substate

        if gbl_lock.acquire(blocking=True,
                            timeout=self.lock_timeout):
            loop_was_running = gbl_run_control_loop

            gbl_run_control_loop = True
            gbl_robot_state = BehaviorState.INITIAL_POWERUP
            gbl_robot_substate = BehaviorSubState.STARTING

            gbl_lock.release()
        else:
            log.error('failed to acquire lock')

        if loop_was_running:
            log.warning("control loop thread already active")
            print("control loop thread already active") #TODO remove
        else:

            self.control_loop_thread = threading.Thread(target=control_loop, args=(), daemon=True)
            log.debug("starting control_loop_thread")
            print("starting control_loop_thread") #TODO remove
            self.control_loop_thread.start()

        log.debug("control loop thread returning")
        return

def __walk_test(duration):
    """Debugging test, cause the robot to walk for a few seconds."""

    assert duration > 0.0
    assert duration <= 20.0

    r = Robot()
    r.walk(0.05, 0, 0)
    time.sleep(duration)
    r.set_velocity(0.0, 0.0, 0.0)
    r.stand()
    r.kill()
    return

if __name__ == "__main__":
    logging.basicConfig(filename='quad_controller.log',
                        filemode='w',
                        level=logging.DEBUG)
    __walk_test(10)


from material import Material
import numpy as np

from texture import Texture

class Mesh:
    """
    Simple class to hold a mesh data. Focuses on vertices, faces (indices of vertices for each face)
    and normals.
    """

    def __init__(self, vertices=None, faces=None, normals=None, textureCoords=None, material=Material()):
        """
        Initialises a mesh object.
        :param vertices: A numpy array containing all vertices
        :param faces: [optional] An int array containing the vertex indices for all faces.
        :param normals: [optional] An array of normal vectors, calculated from the faces if not provided.
        :param material: [optional] An object containing the material information for this object
        """
        self.name = 'Unknown'
        self.vertices = vertices
        self.faces = faces
        self.material = material
        self.colors = None
        self.textureCoords = textureCoords
        self.textures = []
        self.tangents = None
        self.binormals = None

        if vertices is not None:
            print('Creating mesh')
            print('- {} vertices'.format(self.vertices.shape[0]))
            if faces is not None:
                print('- {} faces'.format(self.faces.shape[0]))

        if normals is None:
            if faces is None:
                print(
                    '(W) Warning: the current code only calculates normals using the face vector of indices, '
                    'which was not provided here.')
            else:
                self.calculate_normals()
        else:
            self.normals = normals

        if material.texture is not None:
            self.textures.append(Texture(material.texture))

    def calculate_normals(self):
        """
        method to calculate normals from the mesh faces.
        first, calculate normal for each face using cross product
        then, set each vertex normal as the average of the normals over all faces it belongs to.
        """

        self.normals = np.zeros((self.vertices.shape[0], 3), dtype='f')
        if self.textureCoords is not None:
            self.tangents = np.zeros((self.vertices.shape[0], 3), dtype='f')
            self.binormals = np.zeros((self.vertices.shape[0], 3), dtype='f')

        for f in range(self.faces.shape[0]):
            # calculate the face normal using the cross product of the triangle's sides
            a = self.vertices[self.faces[f, 1]] - self.vertices[self.faces[f, 0]]
            b = self.vertices[self.faces[f, 2]] - self.vertices[self.faces[f, 0]]
            face_normal = np.cross(a, b)

            # tangent
            if self.textureCoords is not None:
                txa = self.textureCoords[self.faces[f, 1], :] - self.textureCoords[self.faces[f, 0], :]
                txb = self.textureCoords[self.faces[f, 2], :] - self.textureCoords[self.faces[f, 2], :]
                face_tangent = txb[0] * a - txa[0] * b
                face_binormal = -txb[1] * a + txa[1] * b

            # blend normal on all 3 vertices
            for j in range(3):
                self.normals[self.faces[f, j], :] += face_normal
                if self.textureCoords is not None:
                    self.tangents[self.faces[f, j], :] += face_tangent
                    self.binormals[self.faces[f, j], :] += face_binormal

        # normalize the vectors
        self.normals /= np.linalg.norm(self.normals, axis=1, keepdims=True)
        if self.textureCoords is not None:
            self.tangents /= np.linalg.norm(self.tangents, axis=1, keepdims=True)
            self.binormals /= np.linalg.norm(self.binormals, axis=1, keepdims=True)

class CubeMesh(Mesh):
    def __init__(self, texture=None, inside=False):

        vertices = np.array([

            [-1.0, -1.0, -1.0],  # 0
            [+1.0, -1.0, -1.0],  # 1

            [-1.0, +1.0, -1.0],  # 2
            [+1.0, +1.0, -1.0],  # 3

            [-1.0, -1.0, +1.0],  # 4
            [-1.0, +1.0, +1.0],  # 5

            [+1.0, -1.0, +1.0],  # 6
            [+1.0, +1.0, +1.0]  # 7

        ], dtype='f')

        faces = np.array([

            # back
            [1, 0, 2],
            [1, 2, 3],

            # right
            [2, 0, 4],
            [2, 4, 5],

            # left
            [1, 3, 7],
            [1, 7, 6],

            # front
            [5, 4, 6],
            [5, 6, 7],

            # bottom
            [0, 1, 4],
            [4, 1, 6],

            # top
            [2, 5, 3],
            [5, 7, 3],

        ], dtype=np.uint32)

        if inside:
            faces = faces[:, np.argsort([0, 2, 1])]

        textureCoords = None  # np.array([], dtype='f')

        Mesh.__init__(self, vertices=vertices, faces=faces, textureCoords=textureCoords)

        if texture is not None:
            self.textures = [
                texture
            ]


#https://www.baeldung.com/java-least-common-multiple#:~:text=Using%20the%20Euclidean%20Algorithm,-There's%20an%20interesting&text=As%20stated%2C%20gcd(a%2C,gcd(a%2Cb).
#https://site.ada.edu.az/~medv/acm/Docs%20e-olimp/Volume%2012/1155_English.htm

import math

def gcde(a, b, d, x, y):
    if (b == 0):
        d = a
        x = 1
        y = 0

        return a, b, d, x, y

    _, _, d, x, y = gcde(b, a % b, d, x, y)

    s = y
    y = x - math.floor((a / b)) * y
    x = s

    return a, b, d, x, y

while True:
    try:
        args = list(map(lambda x : int(x), input().split(" ")))

        a = args[0]
        b = args[1]

        a, b, d, x, y = gcde(a, b, a, 0, 0)

        print(x, y, d)
    except EOFError:
        break


#UTAT - Orbital Subsystem

#Jai Willems, 1006342165
#University of Toronto Faculty of Applied Science and Engineering
#Division of Engineeing Science
#Completed 18-08-2020

#The purpose of this script is to create a orbital simulation program.

#---------------------------------------------------------------------------------------------------------------------------------

import numpy as np

initialPosition = np.array([0, 0, 200000])      #m
mass = 2000                                     #g
initialVelocity = np.array([761.1111, 0, 0])    #m/s
endTime = 5                                     #s
deltaT = 0.5                                    #s

class satellite():
    def __init__(self, position, mass, velocity):
        self.position = position
        self.mass = mass
        self.velocity = velocity
        self.momentum = np.dot(self.mass, self.velocity)

    def __str__(self):
        return('Position: (%.4f, %.4f, %.4f)\tVelocity: (%.4f, %.4f, %.4f)\tMass: %0.4f' %(self.position[0], self.position[1], self.position[2], self.velocity[0], self.velocity[1], self.velocity[2], self.mass))

    def updatePosition(self, netForce, deltaT):
        self.position = self.position + np.dot(netForce, deltaT)

class forces():
    def __init__(self):
        self.forces = np.array([[0, 0, 0]])

    def addEarthGravity(self, satObject):
        G = 0.000000000066733
        mEarth = 5972000000000000000000000
        mSat = satObject.mass
        dist = np.linalg.norm(satObject.position)
        constant = - (G * mEarth * mSat) / dist**2
        gravityForce = np.dot(constant, satObject.position)
        self.forces = np.append(self.forces, [gravityForce], axis=0)

    def findNetForce(self):
        return np.sum(self.forces, axis=0)

satObject = satellite(initialPosition, mass, initialVelocity)
currentTime = 0
while currentTime < endTime:

    appliedForces = forces()
    appliedForces.addEarthGravity(satObject)
    Fnet = appliedForces.findNetForce()

    satObject.updatePosition(Fnet, deltaT)
    print(satObject)

    currentTime += deltaT


# Binary Search

from collections import List

class Solution:
    def maxLength(self, ribbons: List[int], k: int) -> int:

        low, high = 1, max(ribbons)

        while low <= high:

            mid = low + (high - low) // 2

            # add number of ribons that can be made
            num_cuts = sum(ribbon//mid for ribbon in ribbons)
            print(num_cuts, mid)
            # 3 5
            # 2 7
            # 2 6

            # keep searching for a possibly larger size that can make k
            if num_cuts >= k:
                low = mid + 1
            else:
                high = mid - 1

        return low - 1


import operator

from typing import Union, Tuple, Callable, TypeVar
from numbers import Number
from decimal import Decimal, ROUND_DOWN
from functools import total_ordering

from tensortrade.core.exceptions import (
    InvalidNegativeQuantity,
    IncompatibleInstrumentOperation,
    InvalidNonNumericQuantity,
    QuantityOpPathMismatch
)

T = TypeVar("T")

@total_ordering
class Quantity:
    """A size of a financial instrument for use in trading.

    Parameters
    ----------
    instrument : `Instrument`
        The unit of the quantity.
    size : `Decimal`
        The number of units of the instrument.
    path_id : str, optional
        The path order_id that this quantity is allocated for and associated
        with.

    Raises
    ------
    InvalidNegativeQuantity
        Raised if the `size` of the quantity being created is negative.
    """

    def __init__(self, instrument: 'Instrument', size: Decimal, path_id: str = None):
        if size < 0:
            if abs(size) > Decimal(10)**(-instrument.precision):
                raise InvalidNegativeQuantity(float(size))
            else:
                size = 0

        self.instrument = instrument
        self.size = size if isinstance(size, Decimal) else Decimal(size)
        self.path_id = path_id

    @property
    def is_locked(self) -> bool:
        """If quantity is locked for an order. (bool, read-only)"""
        return bool(self.path_id)

    def lock_for(self, path_id: str) -> "Quantity":
        """Locks a quantity for an `Order` identified associated with `path_id`.

        Parameters
        ----------
        path_id : str
            The identification of the order path.

        Returns
        -------
        `Quantity`
            A locked quantity for an order path.
        """
        return Quantity(self.instrument, self.size, path_id)

    def convert(self, exchange_pair: "ExchangePair") -> "Quantity":
        """Converts the quantity into the value of another instrument based
        on its exchange rate from an exchange.

        Parameters
        ----------
        exchange_pair : `ExchangePair`
            The exchange pair to use for getting the quoted price to perform
            the conversion.

        Returns
        -------
        `Quantity`
            The value of the current quantity in terms of the quote instrument.
        """
        if self.instrument == exchange_pair.pair.base:
            instrument = exchange_pair.pair.quote
            converted_size = self.size / exchange_pair.price
        else:
            instrument = exchange_pair.pair.base
            converted_size = self.size * exchange_pair.price
        return Quantity(instrument, converted_size, self.path_id)

    def free(self) -> "Quantity":
        """Gets the free version of this quantity.

        Returns
        -------
        `Quantity`
            The free version of the quantity.
        """
        return Quantity(self.instrument, self.size)

    def quantize(self) -> "Quantity":
        """Computes the quantization of current quantity in terms of the instrument's
        precision.

        Returns
        -------
        `Quantity`
            The quantized quantity.
        """
        return Quantity(self.instrument,
                        self.size.quantize(Decimal(10)**-self.instrument.precision),
                        self.path_id)

    def as_float(self) -> float:
        """Gets the size as a `float`.

        Returns
        -------
        float
            The size as a floating point number.
        """
        return float(self.size)

    def contain(self, exchange_pair: "ExchangePair"):
        """Contains the size of the quantity to be compatible with the settings
        of a given exchange.

        Parameters
        ----------
        exchange_pair : `ExchangePair`
            The exchange pair containing the exchange the quantity must be
            compatible with.

        Returns
        -------
        `Quantity`
            A quantity compatible with the given exchange.
        """
        options = exchange_pair.exchange.options
        price = exchange_pair.price

        if exchange_pair.pair.base == self.instrument:
            size = self.size
            return Quantity(self.instrument, min(size, options.max_trade_size), self.path_id)

        size = self.size * price
        if size < options.max_trade_size:
            return Quantity(self.instrument, self.size, self.path_id)

        max_trade_size = Decimal(options.max_trade_size)
        contained_size = max_trade_size / price
        contained_size = contained_size.quantize(Decimal(10)**-self.instrument.precision, rounding=ROUND_DOWN)
        return Quantity(self.instrument, contained_size, self.path_id)

    @staticmethod
    def validate(left: "Union[Quantity, Number]",
                 right: "Union[Quantity, Number]") -> "Tuple[Quantity, Quantity]":
        """Validates the given left and right arguments of a numeric or boolean
        operation.

        Parameters
        ----------
        left : `Union[Quantity, Number]`
            The left argument of an operation.
        right : `Union[Quantity, Number]`
            The right argument of an operation.

        Returns
        -------
        `Tuple[Quantity, Quantity]`
            The validated quantity arguments to use in a numeric or boolean
            operation.

        Raises
        ------
        IncompatibleInstrumentOperation
            Raised if the instruments left and right quantities are not equal.
        QuantityOpPathMismatch
            Raised if
                - One argument is locked and the other argument is not.
                - Both arguments are locked quantities with unequal path_ids.
        InvalidNonNumericQuantity
            Raised if either argument is a non-numeric object.
        Exception
            If the operation is not valid.
        """
        if isinstance(left, Quantity) and isinstance(right, Quantity):
            if left.instrument != right.instrument:
                raise IncompatibleInstrumentOperation(left, right)

            if (left.path_id and right.path_id) and (left.path_id != right.path_id):
                raise QuantityOpPathMismatch(left.path_id, right.path_id)

            elif left.path_id and not right.path_id:
                right.path_id = left.path_id

            elif not left.path_id and right.path_id:
                left.path_id = right.path_id

            return left, right

        elif isinstance(left, Number) and isinstance(right, Quantity):
            left = Quantity(right.instrument, left, right.path_id)
            return left, right

        elif isinstance(left, Quantity) and isinstance(right, Number):
            right = Quantity(left.instrument, right, left.path_id)
            return left, right

        elif isinstance(left, Quantity):
            raise InvalidNonNumericQuantity(right)

        elif isinstance(right, Quantity):
            raise InvalidNonNumericQuantity(left)

        raise Exception(f"Invalid quantity operation arguments: {left} and {right}")

    @staticmethod
    def _bool_op(left: "Union[Quantity, Number]",
                 right: "Union[Quantity,Number]",
                 op: "Callable[[T, T], bool]") -> bool:
        """Performs a generic boolean operation on two quantities.

        Parameters
        ----------
        left : `Union[Quantity, Number]`
            The left argument of the operation.
        right : `Union[Quantity, Number]`
            The right argument of the operation.
        op : `Callable[[T, T], bool]`
            The boolean operation to be used.

        Returns
        -------
        bool
            The result of performing `op` on with `left` and `right`.
        """
        left, right = Quantity.validate(left, right)
        boolean = op(left.size, right.size)
        return boolean

    @staticmethod
    def _math_op(left: "Union[Quantity, Number]",
                 right: "Union[Quantity, Number]",
                 op: "Callable[[T, T], T]") -> "Quantity":
        """Performs a generic numeric operation on two quantities.

        Parameters
        ----------
        left : `Union[Quantity, Number]`
            The left argument of the operation.
        right : `Union[Quantity, Number]`
            The right argument of the operation.
        op : `Callable[[T, T], bool]`
            The numeric operation to be used.

        Returns
        -------
        `Quantity`
            The result of performing `op` on with `left` and `right`.
        """
        left, right = Quantity.validate(left, right)
        size = op(left.size, right.size)
        return Quantity(left.instrument, size, left.path_id)

    def __add__(self, other: "Quantity") -> "Quantity":
        return Quantity._math_op(self, other, operator.add)

    def __sub__(self, other: "Union[Quantity, Number]") -> "Quantity":
        return Quantity._math_op(self, other, operator.sub)

    def __iadd__(self, other: "Union[Quantity, Number]") -> "Quantity":
        return Quantity._math_op(self, other, operator.iadd)

    def __isub__(self, other: "Union[Quantity, Number]") -> "Quantity":
        return Quantity._math_op(self, other, operator.isub)

    def __mul__(self, other: "Union[Quantity, Number]") -> "Quantity":
        return Quantity._math_op(self, other, operator.mul)

    def __rmul__(self, other: "Union[Quantity, Number]") -> "Quantity":
        return Quantity.__mul__(self, other)

    def __lt__(self, other: "Union[Quantity, Number]") -> bool:
        return Quantity._bool_op(self, other, operator.lt)

    def __eq__(self, other: "Union[Quantity, Number]") -> bool:
        return Quantity._bool_op(self, other, operator.eq)

    def __ne__(self, other: "Union[Quantity, Number]") -> bool:
        return Quantity._bool_op(self, other, operator.ne)

    def __neg__(self) -> bool:
        return operator.neg(self.size)

    def __str__(self) -> str:
        s = "{0:." + str(self.instrument.precision) + "f}" + " {1}"
        s = s.format(self.size, self.instrument.symbol)
        return s

    def __repr__(self) -> str:
        return str(self)


import cv2
import numpy as np
import rect

image = cv2.imread('test-images/page.jpg')

# Resizing will make results better. Why?????
image = cv2.resize(image, (1500, 880))

orig = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
kernel = np.ones((3, 3), np.uint8)
dilated = cv2.dilate(blurred, kernel, iterations=1)
edged = cv2.Canny(dilated, 0, 50)
orig_edged = edged.copy()

# find the contours in the edged image, keeping only the
# largest ones, and initialize the screen contour
_, contours, _ = cv2.findContours(edged, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
print(cv2.contourArea(contours[0]))
cv2.drawContours(image, [contours[0]], -1, (0, 255, 0), 2)

#x,y,w,h = cv2.boundingRect(contours[0])
#cv2.rectangle(image,(x,y),(x+w,y+h),(0,0,255),0)

# get approximate contour
for c in contours:
    p = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * p, True)

    # If there are 4 sides. (Can it be improved here???)
    if len(approx) == 4:
        target = approx
        break

# mapping target points to 800x800 quadrilateral
approx = rect.rectify(target)
pts2 = np.float32([[0,0],[800,0],[800,800],[0,800]])

M = cv2.getPerspectiveTransform(approx,pts2)
dst = cv2.warpPerspective(orig,M,(800,800))

cv2.drawContours(image, [target], -1, (0, 255, 0), 2)
dst = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)

# using thresholding on warped image to get scanned effect (If Required)
ret,th1 = cv2.threshold(dst,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(dst,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
            cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(dst,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)
ret2,th4 = cv2.threshold(dst,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# cv2.imshow("Original.jpg", orig)
# cv2.imshow("Original Gray.jpg", gray)
# cv2.imshow("Original Blurred.jpg", blurred)
cv2.imshow("Original Edged.jpg", orig_edged)
cv2.imshow("Outline.jpg", image)
# cv2.imshow("Thresh Binary.jpg", th1)
# cv2.imshow("Thresh mean.jpg", th2)
# cv2.imshow("Thresh gauss.jpg", th3)
# cv2.imshow("Otsu's.jpg", th4)
cv2.imshow("dst.jpg", dst)

# other thresholding methods
"""
ret,thresh1 = cv2.threshold(dst,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(dst,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(dst,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(dst,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(dst,127,255,cv2.THRESH_TOZERO_INV)

cv2.imshow("Thresh Binary", thresh1)
cv2.imshow("Thresh Binary_INV", thresh2)
cv2.imshow("Thresh Trunch", thresh3)
cv2.imshow("Thresh TOZERO", thresh4)
cv2.imshow("Thresh TOZERO_INV", thresh5)
"""

cv2.waitKey(0)
cv2.destroyAllWindows()


# ******************************************************************************
## GLOBIO - https://www.globio.info
## PBL Netherlands Environmental Assessment Agency - https://www.pbl.nl.
## Reuse permitted under Gnu Public License, GPL v3.
# ******************************************************************************

import os
import numpy as np

import GlobioModel.Core.Error as Err
import GlobioModel.Core.Globals as GLOB
import GlobioModel.Core.Logger as Log
import GlobioModel.Core.Monitor as MON

from GlobioModel.Core.CalculationBase import CalculationBase
from GlobioModel.Core.Raster import Raster
import GlobioModel.Core.RasterUtils as RU

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Step 9
class GLOBIO_CalcAquaticWetlandFloodplainFRHLUMSA(CalculationBase):
  """
  Creates a raster with the aquatic landuse MSA for wetlands and floodplains.
  """

  #-------------------------------------------------------------------------------
  def run(self,*args):
    """
    IN EXTENT Extent
    IN CELLSIZE CellSize
    IN RASTER FRHLU
    IN RASTER WetlandFractions
    IN RASTER FloodplainFractions
    IN FLOAT FACTOR_A
    OUT RASTER FloodplainFRHLUMSA
    """
    self.showStartMsg(args)

    # Check number of arguments.
    if len(args)<=5:
      Err.raiseGlobioError(Err.InvalidNumberOfArguments2,len(args),self.name)

    # Get arguments.
    extent = args[0]
    cellSize = args[1]
    frhluRasterName = args[2]
    wetlandFractionsRasterName = args[3]
    floodplainFractionsRasterName = args[4]
    FACTOR_A = args[5]
    outRasterName = args[6]

    # Check arguments.
    self.checkExtent(extent)
    self.checkCellSize(cellSize)
    self.checkRaster(frhluRasterName)
    self.checkRaster(wetlandFractionsRasterName)
    self.checkRaster(floodplainFractionsRasterName)
    self.checkFloat(FACTOR_A,-100.0,100.0)
    self.checkRaster(outRasterName,True)

    # Align extent.
    extent = RU.alignExtent(extent,cellSize)

    # Set members.
    self.extent = extent
    self.cellSize = cellSize
    self.outDir = os.path.dirname(outRasterName)

    # Enable monitor en show memory and disk space usage.
    MON.showMemDiskUsage(Log,"- ","",self.outDir)

    #-----------------------------------------------------------------------------
    # Read the fractions raster, prepare and select.
    #-----------------------------------------------------------------------------

    # Reads the raster and resizes to extent and resamples to cellsize.
    wetlandFractionsRaster = self.readAndPrepareInRaster(extent,cellSize,wetlandFractionsRasterName,"wetland fractions")

    # Create the mask. Select fraction > 0.0.
    Log.info("Selecting wetland fractions...")
    wetlandFracMask = (wetlandFractionsRaster.r > 0.0)

    # Close and free the fractions raster.
    wetlandFractionsRaster.close()
    wetlandFractionsRaster = None

    #-----------------------------------------------------------------------------
    # Read the fractions raster, prepare and select.
    #-----------------------------------------------------------------------------

    # Reads the raster and resizes to extent and resamples to cellsize.
    floodplainFractionsRaster = self.readAndPrepareInRaster(extent,cellSize,floodplainFractionsRasterName,"floodplain fractions")

    # Create the mask. Select fraction > 0.0.
    Log.info("Selecting floodplain fractions...")
    floodplainFracMask = (floodplainFractionsRaster.r > 0.0)

    # Close and free the fractions raster.
    floodplainFractionsRaster.close()
    floodplainFractionsRaster = None

    #-----------------------------------------------------------------------------
    # Combine wetland and floodplain masks.
    #-----------------------------------------------------------------------------

    # Combine masks.
    fracMask = np.logical_or(wetlandFracMask,floodplainFracMask)

    # Cleanup masks.
    wetlandFracMask = None
    floodplainFracMask = None

    #-----------------------------------------------------------------------------
    # Read the FRHLU raster, prepare and select.
    #-----------------------------------------------------------------------------

    # Reads the raster and resizes to extent and resamples to cellsize.
    frhluRaster = self.readAndPrepareInRaster(extent,cellSize,frhluRasterName,"FRHLU")

    #-----------------------------------------------------------------------------
    # Create output raster.
    #-----------------------------------------------------------------------------

    # Create MSA raster.
    # Initialize with NoData.
    Log.info("Creating MSA raster...")
    noDataValue = -999.0
    outRaster = Raster(outRasterName)
    outRaster.initRaster(extent,cellSize,np.float32,noDataValue)

    #-----------------------------------------------------------------------------
    # Calculate river FRHLU MSA.
    #-----------------------------------------------------------------------------

    # Get data mask.
    dataMask = frhluRaster.getDataMask()

    # Combine masks.
    dataMask = np.logical_and(fracMask,dataMask)

    # Cleanup mask.
    fracMask = None

    # MSA = 1.0 - a * FRHLU
    outRaster.r[dataMask] = frhluRaster.r[dataMask]
    outRaster.r[dataMask] *= FACTOR_A
    outRaster.r[dataMask] *= -1.0
    outRaster.r[dataMask] += 1.0

    # Cleanup mask.
    dataMask = None

    #-----------------------------------------------------------------------------
    # Save output.
    #-----------------------------------------------------------------------------

    # Save the MSA raster for wetland/floodplain FRHLU.
    Log.info("Writing MSA for wetland/floodplain FRHLU...")
    outRaster.write()

    # Cleanup.
    outRaster.close()
    outRaster = None

    # Show used memory and disk space.
    MON.showMemDiskUsage()

    self.showEndMsg()

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
if __name__ == "__main__":
  try:
    outDir = r"C:\Temp\_Globio4\out"
    if not os.path.isdir(outDir):
      outDir = r"G:\Data\out_v3"
    inDir = outDir

    pCalc = GLOBIO_CalcAquaticWetlandFloodplainFRHLUMSA()

    ext = GLOB.extent_World
    cs = GLOB.cellSize_30sec
    lu = os.path.join(inDir,"frhlu.tif")
    wfrac = os.path.join(inDir,"wetland_fractions.tif")
    ffrac = os.path.join(inDir,"floodplain_fractions.tif")
    F_A = 0.87
    out = os.path.join(outDir,"wetlandfloodplain_frhlu_msa.tif")

    if RU.rasterExists(out):
      RU.rasterDelete(out)

    pCalc.run(ext,cs,lu,wfrac,ffrac,F_A,out)
  except:
    Log.err()


# -*- mode: python -*-
# vi: set ft=python :

"""
Downloads a precompiled version of buildifier and makes it available to the
WORKSPACE.

Example:
    WORKSPACE:
        load("@drake//tools/workspace:mirrors.bzl", "DEFAULT_MIRRORS")
        load("@drake//tools/workspace/buildifier:repository.bzl", "buildifier_repository")  # noqa
        buildifier_repository(name = "foo", mirrors = DEFAULT_MIRRORS)

    BUILD:
        sh_binary(
            name = "foobar",
            srcs = ["bar.sh"],
            data = ["@foo//:buildifier"],
        )

Argument:
    name: A unique name for this rule.
"""

load("@drake//tools/workspace:os.bzl", "determine_os")

def _impl(repository_ctx):
    os_result = determine_os(repository_ctx)

    if os_result.error != None:
        fail(os_result.error)

    version = "0.15.0"

    if os_result.is_macos:
        filename = "buildifier.osx"
        sha256 = "860378a2badba9517e523e20f152ef1ca16234e0ca462a1d71e5dbee7d506771"  # noqa
    elif os_result.is_ubuntu:
        filename = "buildifier"
        sha256 = "0dea01a7a511797878f486e6ed8e549980c0710a0a116c8ee953d4e26de41515"  # noqa
    else:
        fail("Operating system is NOT supported", attr = os_result)

    urls = [
        x.format(version = version, filename = filename)
        for x in repository_ctx.attr.mirrors.get("buildifier")
    ]
    output = repository_ctx.path("buildifier")

    repository_ctx.download(urls, output, sha256, executable = True)

    content = """# -*- python -*-

licenses(["notice"])  # Apache-2.0

exports_files(
    ["buildifier"],
)
"""

    repository_ctx.file("BUILD.bazel", content, executable = False)

buildifier_repository = repository_rule(
    attrs = {
        "mirrors": attr.string_list_dict(),
    },
    implementation = _impl,
)


import time
import pytest

from botocore.exceptions import ClientError
from botocore.stub import ANY

import service_linked_roles

@pytest.mark.parametrize(
    "error_code, stop_on_method",
    [
        (None, None),
        ("TestException", "stub_create_service_linked_role"),
        ("TestException", "stub_list_attached_role_policies"),
        ("TestException", "stub_get_policy"),
        ("TestException", "stub_get_policy_version"),
        ("TestException", "stub_delete_service_linked_role"),
    ],
)
def test_create_service_linked_role(
    make_stubber, stub_runner, monkeypatch, error_code, stop_on_method
):
    iam_stubber = make_stubber(service_linked_roles.iam.meta.client)

    service_name = "test-service-name"
    role_name = "test-role"
    policy = {"test-policy": "arn:aws:iam::111122223333:policy/test-policy"}
    policy_ver_id = "test-version"
    task_id = "test-task"

    inputs = [service_name, "y"]
    monkeypatch.setattr("builtins.input", lambda x: inputs.pop(0))
    monkeypatch.setattr(time, "sleep", lambda x: None)

    with stub_runner(error_code, stop_on_method) as runner:
        runner.add(
            iam_stubber.stub_create_service_linked_role, service_name, ANY, role_name
        )
        runner.add(iam_stubber.stub_list_attached_role_policies, role_name, policy)
        runner.add(iam_stubber.stub_get_policy, policy["test-policy"], policy_ver_id)
        runner.add(
            iam_stubber.stub_get_policy_version,
            policy["test-policy"],
            policy_ver_id,
            "test-document",
        )
        runner.add(iam_stubber.stub_delete_service_linked_role, role_name, task_id)
        runner.add(
            iam_stubber.stub_get_service_linked_role_deletion_status,
            task_id,
            "IN_PROGRESS",
        )
        runner.add(
            iam_stubber.stub_get_service_linked_role_deletion_status,
            task_id,
            "SUCCEEDED",
        )

    if error_code is None:
        service_linked_roles.usage_demo()
    else:
        with pytest.raises(ClientError) as exc_info:
            service_linked_roles.usage_demo()
        assert exc_info.value.response["Error"]["Code"] == error_code


import pygame,sys
from esTxt import textt
def options():
    altura = 600
    largura = 1230
    mainClock = pygame.time.Clock()
    screen = pygame.display.set_mode((largura,altura), 0, 32)
    es=textt("opt",(225,225,225),screen,40,400)
    running = True
    while running:
        screen.fill((0,0,0))
        es.draw_text1()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
        pygame.display.update()
        mainClock.tick(60)


import urllib.request
import shutil
import requests
import xlsxwriter
import random
import re

from pymongo import MongoClient
client = MongoClient()
db = client.chrome
collect = db.polyA

mcursor = collect.find({'organism':'Homo sapiens'})

TotalConsmRNA = {}
RegionConsmRNA = {}

ConsGene = {}
RegionConsGene = {}

TotalmRNA = {}
TotalGene = {}

REGION_START = 22
REGION_END = 33

for record in mcursor:
	gene = record["gene_id"]
	if record["gene_id"] in TotalGene:
		TotalGene[gene] += 1
	else:
		TotalGene[gene] = 1
	for mrna in record["mrna"]:
		if mrna in TotalmRNA:
			TotalmRNA[mrna] += 1
		else:
			TotalmRNA[mrna] = 1
	for URS in record["URS"]:
		pos = URS["downstream_rel_pos"]
		if "conserved" in URS:
			gene = record["gene_id"]
			if record["gene_id"] in ConsGene:
				ConsGene[gene] += 1
			else:
				ConsGene[gene] = 1
			for mrna in record["mrna"]:
				if mrna in TotalConsmRNA:
					TotalConsmRNA[mrna] += 1
				else:
					TotalConsmRNA[mrna] = 1
			break
	for URS in record["URS"]:
		pos = URS["downstream_rel_pos"]
		if "conserved" in URS:
			if pos <= REGION_END and pos >= REGION_START:
				gene = record["gene_id"]
				if record["gene_id"] in RegionConsGene:
					RegionConsGene[gene] += 1
				else:
					RegionConsGene[gene] = 1
				for mrna in record["mrna"]:
					if mrna in RegionConsmRNA:
						RegionConsmRNA[mrna] += 1
					else:
						RegionConsmRNA[mrna] = 1
				break
print ("Total mRNA w/ Cons URS : " + str(len(TotalConsmRNA)))
print ("mRNA w/ Cons URS in Region (" + str(REGION_START) + "-"+ str(REGION_END) + ") nt : "+ str(len(RegionConsmRNA)))
print ("Total genes w/ Cons URS : " + str(len(ConsGene)))
print ("genes w/ Cons URS in Region (" + str(REGION_START) + "-"+ str(REGION_END) + ") nt : "+ str(len(RegionConsGene)))
print ("Total mRNA we have polyA data for: " + str(len(TotalmRNA)))
print ("Total genes we have polyA data for: " + str(len(TotalGene)))


# Your code goes here
# Tracy Cook; Assignment 1; March 5, 2016

import csv

# Open our input and output files
csvfile = open('cleanme.csv', 'r')
outfile = open('cleanme-clean.csv', 'w')

# Now a DictReader and DictWriter
# DictReader and DictWriter are imported libraries
reader = csv.DictReader(csvfile)
writer = csv.DictWriter(outfile, reader.fieldnames)

# DictWriter writes to outfile
# reader.fieldname refers to the headers

# Write headers
writer.writeheader()

# Everything above this is setup
# Clean and write the data to output
for row in reader:
	#print row['first_name']
	# We are overwriting something that already exists
    row['first_name'] = row['first_name'].upper()
    row['zip'] = row['zip'].zfill(5)
    row['city'] = row['city'].replace(' ', ' ')
    if float(row['amount']) > 1000.0:

    	writer.writerow(row)


"""
A module for calculating pharmacies' current respond time.
"""

import datetime
import numpy as np
import pandas as pd
from scipy.interpolate import interp1d
from scipy.signal import medfilt
from scipy.signal import savgol_filter
import ext_connections as ext_con
import requests
import os
import json

class RespondTime:
    """
    A class for calculating pharmacies' current respond time.

    The class is designed for realtime calculating and previous calculation
    of pharmacy's respond time. After input data is received
    it's possible to create calculation math. models.

    There are 3 math. models represented:
    1. Linear interpolation with a median smoothing (a large sample of data)
    2. Linear interpolation with a Savitzky-Golay smoothing (a medium sample of data)
    3. Canonical averaging (a small sample of data)
    """

    def __init__(self):
        self.settings = RespondSettings()
        self.connection = ext_con.API()

    @property
    def connection(self):
        return self._connection

    @connection.setter
    def connection(self, connection):
        self._connection = connection

    def get_pharmacy_data_table(self, sn):
        """Creates an input table from server"""

        query = self.settings.get_setting('reaction_api')
        if not query:
            return None

        query += '/?sn=' + str(sn)

        auth = self.settings.get_setting('auth')
        if not auth:
            return None

        method = 'GET'
        headers = {
            "Authorization": ' '.join(["Basic", auth]),
            "Content-type": "application/json",
            "Accept": "application/json"
        }
        file_type = 'json'
        table = self.connection.execute(query, method, headers, file_type)

        if table is None or table.empty:
            return None

        table.FixedDT = table.FixedDT.apply(pd.to_datetime)
        table.TotalTimeSec = table.TotalTimeSec.astype(int)
        table['Hour'] = table['FixedDT'].dt.hour
        table['Minute'] = table['FixedDT'].dt.minute
        table['Weekday'] = table['FixedDT'].dt.dayofweek + 1

        return table

    def get_pharmacies_schedules(self):
        query = self.settings.get_setting('schedule_api')
        if not query:
            return None

        auth = self.settings.get_setting('auth')
        if not auth:
            return None

        method = 'GET'
        headers = {
            "Authorization": ' '.join(["Basic", auth]),
            "Content-type": "application/json",
            "Accept": "application/json"
        }
        file_type = 'json'
        table = self.connection.execute(query, method, headers, file_type)

        return table

    def get_respond_time_table(self, return_full_table=False):
        """Returns a table of calculated respond time"""

        interval_mins = int(self.settings.get_setting('interval_minutes'))
        min_res = int(self.settings.get_setting('min_res'))
        max_res = int(self.settings.get_setting('max_res'))

        current_sn = 0
        sn_str = self.settings.get_setting('pharmacy_sn')
        if sn_str:
            current_sn = int(sn_str)

        columns = ['ID_Branch', 'Weekday', 'StartTime', 'EndTime', 'RespondTime']
        result_table = pd.DataFrame([], columns=columns)
        full_table = pd.DataFrame([], columns=columns)

        is_sent = False
        sent_count = 0
        schedule_table = self.get_pharmacies_schedules()
        count = len(schedule_table)
        print(count, 'pharmacies:')
        for index, row in schedule_table.iterrows():
            is_sent = False

            num = index + 1
            pharmacy_sn = int(row.SerialNumber)
            if current_sn and pharmacy_sn != current_sn:
                continue

            print(str(pharmacy_sn), '[', str(num), ']')

            pharmacy_table = self.get_pharmacy_data_table(pharmacy_sn)
            if pharmacy_table is None or pharmacy_table.empty:
                continue
            elif len(pharmacy_table) <= min_res:
                schedule = row.Schedule.split(';')
                respond_table = self._calculate_small_sample(pharmacy_table, interval_mins, schedule)
            elif len(pharmacy_table) <= max_res:
                respond_table = self._calculate_medium_sample(pharmacy_table, interval_mins)
            else:
                respond_table = self._calculate_large_sample(pharmacy_table, interval_mins)

            if respond_table is None:
                continue

            pharmacy_id = row.ID
            respond_table['ID_Branch'] = pharmacy_id

            result_table = result_table.append(respond_table, sort=False)
            if return_full_table:
                full_table = full_table.append(respond_table, sort=False)
            sent_count += 1

            if sent_count == 5 or (num == count and not result_table.empty):
                self.send_data(result_table)
                sent_count = 0
                result_table = pd.DataFrame([], columns=columns)
                is_sent = True

        if not is_sent and not result_table.empty:
            self.send_data(result_table)

        print('Calculated respond time and sent to the server... ' + datetime.datetime.now().strftime('%H:%M:%S'))

        return full_table

    def send_data(self, table: pd.DataFrame):
        """Sends data of calculated respond time to the SQL using API"""

        if table is None or table.empty:
            return False

        query = self.settings.get_setting('send_api')
        if not query:
            return False

        auth = self.settings.get_setting('auth')
        if not auth:
            return False

        table.Weekday = table.Weekday.astype(str)
        table.RespondTime = table.RespondTime.astype(str)
        table.StartTime.map(lambda x: x.strftime('%H:%M:%S'))
        table.EndTime.map(lambda x: x.strftime('%H:%M:%S'))

        items = table.to_json(orient='records')
        items_data = json.loads(items)
        json_data = {'Items': items_data}

        headers = {
            "Authorization": ' '.join(["Basic", auth]),
            "Content-type": "application/json",
            "Accept": "application/json"
        }

        try:
            respond = requests.post(url=query, headers=headers, json=json_data)
            if respond.status_code != 200:
                return False

            json_response = respond.json()
            if json_response['Status'] == 'Error':
                print('Error:', json_response['Description'])
                return False
        except ConnectionResetError as e:
            print('Error:', e)
            return False
        except requests.exceptions.ConnectionError as e:
            print('Error:', e)
            return False

        return True

    def finish(self):
        self.connection.disconnect()

    @staticmethod
    def save_table(table: pd.DataFrame, path):
        """Saves a .csv file of calculated respond time"""

        if not path:
            return ''

        if path[-1] != '\\':
            path += '\\'

        file_date = datetime.datetime.now().strftime('%Y%m%d')
        file_name = 'respond_time_' + file_date
        file_ext = '.csv'
        full_path = path + file_name + file_ext

        table.to_csv(full_path, header=True, index=False)

        return path

    def _calculate_large_sample(self, table: pd.DataFrame, interval_minutes: int):
        """1st math. model"""

        pharmacy_table = table.copy()
        pharmacy_table.sort_values(
            ['Weekday', 'Hour', 'Minute', 'FixedDT'],
            ascending=[True, True, True, True],
            inplace=True
        )

        intervals, responds = self._get_x_y(pharmacy_table)
        median_responds = Smoothing.median_smoothing(responds, 3)

        itp_f = self._get_itp_f(intervals, median_responds)

        new_intervals = np.linspace(intervals.min(), intervals.max(), 10000)
        new_responds = itp_f(new_intervals)
        itp_func = self._get_itp_f(new_intervals, new_responds)

        respond_table = self._get_output_table(itp_func, interval_minutes, by_weekday=True)

        return respond_table

    def _calculate_medium_sample(self, table: pd.DataFrame, interval_minutes: int):
        """2nd math. model"""

        pharmacy_table = table.copy()
        pharmacy_table.sort_values(
            ['Hour', 'Minute', 'FixedDT'],
            ascending=[True, True, True],
            inplace=True
        )

        intervals, responds = self._get_x_y(pharmacy_table, by_weekday=False)

        lin_intervals = np.linspace(intervals.min(), intervals.max(), 1000)
        sg_responds = Smoothing.savgol_smoothing(intervals, responds, lin_intervals)

        itp_func = self._get_itp_f(lin_intervals, sg_responds)

        new_intervals = np.linspace(lin_intervals.min(), lin_intervals.max(), 1000)
        new_responds = itp_func(new_intervals)
        new_itp_func = self._get_itp_f(new_intervals, new_responds)

        respond_table = self._get_output_table(new_itp_func, interval_minutes, by_weekday=False)

        return respond_table

    def _calculate_small_sample(self, table: pd.DataFrame, interval_minutes: int, schedule: list):
        """3rd math. model"""

        if not schedule or not schedule[0]:
            return None

        pharmacy_table = table.copy()
        pharmacy_table.sort_values(
            ['Hour', 'Minute', 'FixedDT'],
            ascending=[True, True, True],
            inplace=True
        )

        # Calculating average respond time
        intervals, responds = self._get_x_y(pharmacy_table, by_weekday=True)

        respond_sum = responds.sum()
        respond_count = len(responds)
        avg_respond = int(respond_sum // respond_count)

        # Constants
        days_per_week = 7
        hours_per_day = 24
        minutes_per_hour = 60
        minutes_per_day = hours_per_day * minutes_per_hour

        time_format = '%H:%M'

        day_end_time_str = str(23) + ':' + str(59)
        day_end_dt = datetime.datetime.strptime(day_end_time_str, time_format)

        day_start_time_str = str(0) + ':' + str(0)
        day_start_dt = datetime.datetime.strptime(day_start_time_str, time_format)

        # Filling in minutes and respond time due to schedule
        minutes_list = []
        respond_list = []
        for weekday in range(days_per_week):
            day_schedule = ''
            if weekday < len(schedule):
                day_schedule = schedule[weekday]

            start_dt, end_dt = self._start_end_time(day_schedule)

            # Find a count of next closed days
            next_start_dt_work = start_dt
            next_closed_days = 0
            for ind in range(days_per_week):
                next_day = weekday + ind + 1
                if next_day >= days_per_week:
                    next_day -= days_per_week

                next_day_schedule = ''
                if next_day < len(schedule):
                    next_day_schedule = schedule[next_day]

                next_start_dt, next_end_dt = self._start_end_time(next_day_schedule)
                if next_start_dt == next_end_dt:
                    next_closed_days += 1
                else:
                    # Nearest work day start in a schedule
                    next_start_dt_work = next_start_dt
                    break

            for hour in range(hours_per_day):
                for minute in np.arange(0, minutes_per_hour, 1):
                    # Current calculating datetime with average respond time included
                    cur_time_str = str(hour) + ':' + str(minute)
                    cur_dt = datetime.datetime.strptime(cur_time_str, time_format)
                    cur_dt_delta = cur_dt + datetime.timedelta(seconds=avg_respond)

                    respond_time = 0
                    if cur_dt_delta >= end_dt:
                        # After schedule day's end
                        diff_from_day_start = (next_start_dt_work - day_start_dt).seconds
                        diff_till_day_end = (day_end_dt - end_dt).seconds
                        diff_schedule = diff_till_day_end + diff_from_day_start
                        diff_cur_to_end = (cur_dt - end_dt).seconds
                        diff_schedule_wo_cur = diff_schedule - diff_cur_to_end
                        if cur_dt < end_dt:
                            diff_cur_to_end = (end_dt - cur_dt).seconds
                            diff_schedule_wo_cur = diff_schedule + diff_cur_to_end
                        diff_total = diff_schedule_wo_cur + avg_respond

                        respond_time = 24 * 60 * 60 * next_closed_days + diff_total
                    elif start_dt < cur_dt_delta < end_dt:
                        # In work day schedule
                        diff_after = (start_dt - cur_dt).seconds
                        if cur_dt > start_dt:
                            diff_after = 0
                        respond_time = diff_after + avg_respond
                    elif cur_dt_delta <= start_dt:
                        # Before schedule day's start
                        diff_before = (start_dt - cur_dt).seconds
                        respond_time = diff_before + avg_respond

                    respond_list.append(respond_time)

                    cur_minute = weekday * minutes_per_day + hour * minutes_per_hour + minute
                    minutes_list.append(cur_minute)

        intervals = np.array(minutes_list)
        responds = np.array(respond_list)

        itp_func = self._get_itp_f(intervals, responds)

        respond_table = self._get_output_table(itp_func, interval_minutes, by_weekday=False)

        return respond_table

    @staticmethod
    def _get_output_table(func: interp1d, interval_minutes: int, by_weekday: bool = True):
        """Gets an output table of calculated respond time"""

        columns = ['Weekday', 'StartTime', 'EndTime', 'RespondTime']
        strings = []

        days_per_week = 7
        hours_per_day = 24
        minutes_per_hour = 60
        minutes_per_day = hours_per_day * minutes_per_hour

        for weekday in range(days_per_week):
            for hour in range(hours_per_day):
                for minute in np.arange(0, minutes_per_hour, interval_minutes):
                    new_weekday = weekday
                    if not by_weekday:
                        new_weekday = 0

                    today_minutes = new_weekday * minutes_per_day + hour * minutes_per_hour + minute
                    start_time = datetime.time(hour, minute, 0)
                    if minute + interval_minutes == minutes_per_hour:
                        if hour + 1 == hours_per_day:
                            end_time = datetime.time(0, 0, 0)
                        else:
                            end_time = datetime.time(hour + 1, 0, 0)
                    else:
                        end_time = datetime.time(hour, minute + interval_minutes, 0)
                    respond_time = int(abs(func(today_minutes)))
                    strings.append([weekday + 1, start_time, end_time, respond_time])

        respond_table = pd.DataFrame(strings, columns=columns)

        return respond_table

    @staticmethod
    def _get_x_y(table: pd.DataFrame, by_weekday: bool = True):
        """Gets x as minute of datetime and y as respond time"""

        minutes_list = []
        respond_list = []

        for index, row in table.iterrows():
            total_minute = (row['Weekday'] - 1 if by_weekday else 0) * 24 * 60 + row['Hour'] * 60 + row['Minute']
            if total_minute in minutes_list:
                continue
            minutes_list.append(total_minute)
            respond_list.append(row['TotalTimeSec'])

        if minutes_list:
            min_minute = 0
            max_minute = (7 if by_weekday else 1) * 24 * 60 - 1

            if min_minute not in minutes_list:
                first_respond = respond_list[0]
                minutes_list.insert(0, min_minute)
                respond_list.insert(0, first_respond)

            if max_minute not in minutes_list:
                last_respond = respond_list[-1]
                minutes_list.append(max_minute)
                respond_list.append(last_respond)

        arr_x = np.array(minutes_list)
        arr_y = np.array(respond_list)

        return arr_x, arr_y

    @staticmethod
    def _get_itp_f(minutes: np.ndarray, respond_time: np.ndarray):
        """Gets an interpolating function respond_time(minutes)"""

        return interp1d(minutes, respond_time, kind='linear')

    @staticmethod
    def _start_end_time(schedule):
        time_format = '%H:%M'
        default_time = datetime.datetime.strptime('0:0', time_format)
        if not schedule:
            return default_time, default_time

        start_end_list = schedule.split('-')

        # Schedule start/end time of weekday
        if start_end_list:
            start_time_str = start_end_list[0].strip()
            try:
                start_dt = datetime.datetime.strptime(start_time_str, time_format)
            except ValueError as e:
                start_dt = default_time
        else:
            start_dt = default_time

        if len(start_end_list) > 1:
            end_time_str = start_end_list[1].strip()
            try:
                end_dt = datetime.datetime.strptime(end_time_str, time_format)
            except ValueError as e:
                end_dt = default_time
        else:
            end_dt = default_time

        return start_dt, end_dt

class RespondSettings:
    _settings = {}

    def __init__(self):
        curr_dir = os.getcwd()
        if curr_dir[-1] != '\\':
            curr_dir += '\\'

        file_name = curr_dir + 'settings.ini'
        with open(file_name, 'r') as file:
            for line in file.readlines():
                set_arr = line.split('=')
                key = set_arr[0].strip()
                value = line.replace(key + '=', '').strip()
                self._settings[key] = value

    def get_setting(self, name):
        return self._settings.get(name, '')

class Smoothing:
    """A class of smoothing methods"""

    @staticmethod
    def savgol_smoothing(
            x: np.ndarray,
            y: np.ndarray,
            new_x: np.ndarray,
            window: int = 101,
            order: int = 3):
        """Savitzky-Golay smoothing"""

        itp_func = interp1d(x, y, kind='linear')
        y_smooth = savgol_filter(itp_func(new_x), window, order)

        return y_smooth

    @staticmethod
    def mab_smoothing(y: np.ndarray, box_pts: int = 3):
        """Moving average boxes smoothing"""

        box = np.ones(box_pts) / box_pts
        y_smooth = np.convolve(y, box, mode='same')

        return y_smooth

    @staticmethod
    def triangle_smoothing(
            y: np.ndarray,
            degree: int = 3,
            drop_values: bool = False):
        """Triangle smoothing"""

        triangle = np.array(list(range(degree)) + [degree] + list(range(degree)[::-1])) + 1
        y_smooth = []

        for i in range(degree, len(y) - degree * 2):
            point = y[i:i + len(triangle)] * triangle
            y_smooth.append(sum(point) / sum(triangle))

        if drop_values:
            return y_smooth

        y_smooth = [y_smooth[0]] * int(degree + degree / 2) + y_smooth
        while len(y_smooth) < len(y):
            y_smooth.append(y_smooth[-1])

        return y_smooth

    @staticmethod
    def median_smoothing(y: np.ndarray, kernel_size: int = 3):
        """Median smoothing"""

        y_smooth = medfilt(y, kernel_size)

        return y_smooth


from fastapi import APIRouter, HTTPException
from models.post import Post
from services.post_service import *

posts = APIRouter()

@posts.get("/posts/")
def get_all_post():
    try:
        return get_all()
    except Exception as e:
        return HTTPException(status_code= 400, detail= str(e))

@posts.get("/posts/{id}")
def get_post_by_id(id):
    try:
        return get_by_id(id)
    except Exception as e:
        return HTTPException(status_code= 400, detail= str(e))

@posts.post("/posts/")
def create(post : Post):
    try:
        create_post(post)
    except Exception as e:
        return HTTPException(status_code= 400, detail= str(e))

@posts.put("/posts/{id}")
def update(id, post: PostUpdateDto):
    try:
        post = update_post(id,post)
        return post
    except Exception as e:
        return HTTPException(status_code= 400, detail= str(e))

@posts.delete("/posts/{id}")
def delete(id):
    try:
        delete_post(id)
    except Exception as e:
        return HTTPException(status_code=400, detail=str(e))