#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Ambre chamber
"""
__author__ = "Dennis van Gils"
__authoremail__ = "vangils.dennis@gmail.com"
__url__ = "https://github.com/Dennis-van-Gils/project-Ambre-chamber"
__date__ = "31-08-2020"
__version__ = "2.0"
# pylint: disable=bare-except, broad-except, try-except-raise

import os
import sys
import time

import numpy as np
import psutil

from PyQt5 import QtCore, QtGui
from PyQt5 import QtWidgets as QtWid
from PyQt5.QtCore import QDateTime
import pyqtgraph as pg

from dvg_debug_functions import tprint, dprint, print_fancy_traceback as pft
from dvg_pyqt_controls import (
    create_LED_indicator,
    create_Toggle_button,
    SS_TEXTBOX_READ_ONLY,
    SS_GROUP,
)
from dvg_pyqt_filelogger import FileLogger
from dvg_pyqtgraph_threadsafe import (
    HistoryChartCurve,
    LegendSelect,
    PlotManager,
)

from dvg_devices.Arduino_protocol_serial import Arduino
from dvg_qdeviceio import QDeviceIO

TRY_USING_OPENGL = True
if TRY_USING_OPENGL:
    try:
        import OpenGL.GL as gl  # pylint: disable=unused-import
    except:
        print("OpenGL acceleration: Disabled")
        print("To install: `conda install pyopengl` or `pip install pyopengl`")
    else:
        print("OpenGL acceleration: Enabled")
        pg.setConfigOptions(useOpenGL=True)
        pg.setConfigOptions(antialias=True)
        pg.setConfigOptions(enableExperimental=True)

# Global pyqtgraph configuration
# pg.setConfigOptions(leftButtonPan=False)
pg.setConfigOption("foreground", "#EEE")

# Constants
# fmt: off
DAQ_INTERVAL_MS    = 1000  # [ms]
CHART_INTERVAL_MS  = 500   # [ms]
CHART_HISTORY_TIME = 3600  # [s]
# fmt: on

# Show debug info in terminal? Warning: Slow! Do not leave on unintentionally.
DEBUG = False

def get_current_date_time():
    cur_date_time = QDateTime.currentDateTime()
    return (
        cur_date_time.toString("dd-MM-yyyy"),  # Date
        cur_date_time.toString("HH:mm:ss"),  # Time
        cur_date_time.toString("yyMMdd_HHmmss"),  # Reverse notation date-time
    )

# ------------------------------------------------------------------------------
#   Arduino state
# ------------------------------------------------------------------------------

class State(object):
    """Reflects the actual readings, parsed into separate variables, of the
    Arduino. There should only be one instance of the State class.
    """

    def __init__(self):
        self.time = np.nan  # [s]
        self.ds18b20_temp = np.nan  # ['C]
        self.dht22_temp = np.nan  # ['C]
        self.dht22_humi = np.nan  # [%]
        self.is_valve_open = False

        # Automatic valve control
        self.humi_threshold = np.nan  # [%]
        self.open_valve_when_super_humi = np.nan

state = State()

# ------------------------------------------------------------------------------
#   MainWindow
# ------------------------------------------------------------------------------

class MainWindow(QtWid.QWidget):
    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

        self.setWindowTitle("Ambre chamber")
        self.setGeometry(350, 50, 960, 800)
        self.setStyleSheet(SS_TEXTBOX_READ_ONLY + SS_GROUP)

        # -------------------------
        #   Top frame
        # -------------------------

        # Left box
        self.qlbl_update_counter = QtWid.QLabel("0")
        self.qlbl_DAQ_rate = QtWid.QLabel("DAQ: nan Hz")
        self.qlbl_DAQ_rate.setStyleSheet("QLabel {min-width: 7em}")

        vbox_left = QtWid.QVBoxLayout()
        vbox_left.addWidget(self.qlbl_update_counter, stretch=0)
        vbox_left.addStretch(1)
        vbox_left.addWidget(self.qlbl_DAQ_rate, stretch=0)

        # Middle box
        self.qlbl_title = QtWid.QLabel(
            "Ambre chamber",
            font=QtGui.QFont("Palatino", 14, weight=QtGui.QFont.Bold),
        )
        self.qlbl_title.setAlignment(QtCore.Qt.AlignCenter)
        self.qlbl_cur_date_time = QtWid.QLabel("00-00-0000    00:00:00")
        self.qlbl_cur_date_time.setAlignment(QtCore.Qt.AlignCenter)
        self.qpbt_record = create_Toggle_button(
            "Click to start recording to file", minimumWidth=300
        )
        # fmt: off
        self.qpbt_record.clicked.connect(lambda state: log.record(state)) # pylint: disable=unnecessary-lambda
        # fmt: on

        vbox_middle = QtWid.QVBoxLayout()
        vbox_middle.addWidget(self.qlbl_title)
        vbox_middle.addWidget(self.qlbl_cur_date_time)
        vbox_middle.addWidget(self.qpbt_record)

        # Right box
        self.qpbt_exit = QtWid.QPushButton("Exit")
        self.qpbt_exit.clicked.connect(self.close)
        self.qpbt_exit.setMinimumHeight(30)
        self.qlbl_recording_time = QtWid.QLabel(alignment=QtCore.Qt.AlignRight)

        vbox_right = QtWid.QVBoxLayout()
        vbox_right.addWidget(self.qpbt_exit, stretch=0)
        vbox_right.addStretch(1)
        vbox_right.addWidget(self.qlbl_recording_time, stretch=0)

        # Round up top frame
        hbox_top = QtWid.QHBoxLayout()
        hbox_top.addLayout(vbox_left, stretch=0)
        hbox_top.addStretch(1)
        hbox_top.addLayout(vbox_middle, stretch=0)
        hbox_top.addStretch(1)
        hbox_top.addLayout(vbox_right, stretch=0)

        # -------------------------
        #   Bottom frame
        # -------------------------

        #  Charts
        # -------------------------

        self.gw = pg.GraphicsLayoutWidget()

        # Plot: Temperature: DS18B20
        p = {"color": "#EEE", "font-size": "10pt"}
        self.pi_ds18b20_temp = self.gw.addPlot(row=0, col=0)
        self.pi_ds18b20_temp.setLabel("left", text="temperature (°C)", **p)

        # Plot: Temperature: DHT 22
        self.pi_dht22_temp = self.gw.addPlot(row=1, col=0)
        self.pi_dht22_temp.setLabel("left", text="temperature (°C)", **p)

        # Plot: Humidity: DHT22
        self.pi_dht22_humi = self.gw.addPlot(row=2, col=0)
        self.pi_dht22_humi.setLabel("left", text="humidity (%)", **p)

        self.plots = [
            self.pi_ds18b20_temp,
            self.pi_dht22_humi,
            self.pi_dht22_temp,
        ]
        for plot in self.plots:
            plot.setClipToView(True)
            plot.showGrid(x=1, y=1)
            plot.setLabel("bottom", text="history (s)", **p)
            plot.setMenuEnabled(True)
            plot.enableAutoRange(axis=pg.ViewBox.XAxis, enable=False)
            plot.enableAutoRange(axis=pg.ViewBox.YAxis, enable=True)
            plot.setAutoVisible(y=True)
            plot.setRange(xRange=[-CHART_HISTORY_TIME, 0])

        # Curves
        capacity = round(CHART_HISTORY_TIME * 1e3 / DAQ_INTERVAL_MS)
        PEN_01 = pg.mkPen(color=[255, 255, 0], width=3)
        PEN_02 = pg.mkPen(color=[0, 255, 255], width=3)

        self.tscurve_ds18b20_temp = HistoryChartCurve(
            capacity=capacity,
            linked_curve=self.pi_ds18b20_temp.plot(
                pen=PEN_01, name="DS18B20 temp."
            ),
        )
        self.tscurve_dht22_temp = HistoryChartCurve(
            capacity=capacity,
            linked_curve=self.pi_dht22_temp.plot(
                pen=PEN_01, name="DHT22 temp."
            ),
        )
        self.tscurve_dht22_humi = HistoryChartCurve(
            capacity=capacity,
            linked_curve=self.pi_dht22_humi.plot(
                pen=PEN_02, name="DHT22 humi."
            ),
        )
        self.tscurves = [
            self.tscurve_ds18b20_temp,
            self.tscurve_dht22_temp,
            self.tscurve_dht22_humi,
        ]

        #  Group `Readings`
        # -------------------------

        legend = LegendSelect(
            linked_curves=self.tscurves, hide_toggle_button=True
        )

        p = {
            "readOnly": True,
            "alignment": QtCore.Qt.AlignRight,
            "maximumWidth": 54,
        }
        self.qlin_ds18b20_temp = QtWid.QLineEdit(**p)
        self.qlin_dht22_temp = QtWid.QLineEdit(**p)
        self.qlin_dht22_humi = QtWid.QLineEdit(**p)

        # fmt: off
        legend.grid.setHorizontalSpacing(6)
        legend.grid.addWidget(self.qlin_ds18b20_temp  , 0, 2)
        legend.grid.addWidget(QtWid.QLabel("± 0.5 °C"), 0, 3)
        legend.grid.addWidget(self.qlin_dht22_temp    , 1, 2)
        legend.grid.addWidget(QtWid.QLabel("± 0.5 °C"), 1, 3)
        legend.grid.addWidget(self.qlin_dht22_humi    , 2, 2)
        legend.grid.addWidget(QtWid.QLabel("± 3 %")   , 2, 3)
        # fmt: on

        qgrp_readings = QtWid.QGroupBox("Readings")
        qgrp_readings.setLayout(legend.grid)

        #  Group 'Log comments'
        # -------------------------

        self.qtxt_comments = QtWid.QTextEdit()
        grid = QtWid.QGridLayout()
        grid.addWidget(self.qtxt_comments, 0, 0)

        qgrp_comments = QtWid.QGroupBox("Log comments")
        qgrp_comments.setLayout(grid)

        #  Group 'Charts'
        # -------------------------

        self.plot_manager = PlotManager(parent=self)
        self.plot_manager.add_autorange_buttons(linked_plots=self.plots)
        self.plot_manager.add_preset_buttons(
            linked_plots=self.plots,
            linked_curves=self.tscurves,
            presets=[
                {
                    "button_label": "00:30",
                    "x_axis_label": "history (sec)",
                    "x_axis_divisor": 1,
                    "x_axis_range": (-30, 0),
                },
                {
                    "button_label": "01:00",
                    "x_axis_label": "history (sec)",
                    "x_axis_divisor": 1,
                    "x_axis_range": (-60, 0),
                },
                {
                    "button_label": "10:00",
                    "x_axis_label": "history (min)",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-10, 0),
                },
                {
                    "button_label": "30:00",
                    "x_axis_label": "history (min)",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-30, 0),
                },
                {
                    "button_label": "60:00",
                    "x_axis_label": "history (min)",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-60, 0),
                },
            ],
        )
        self.plot_manager.add_clear_button(linked_curves=self.tscurves)
        self.plot_manager.perform_preset(1)

        qgrp_chart = QtWid.QGroupBox("Charts")
        qgrp_chart.setLayout(self.plot_manager.grid)

        #  Group 'Valve control'
        # -------------------------

        self.LED_is_valve_open = create_LED_indicator()
        self.qlin_humi_threshold = QtWid.QLineEdit(
            "%d" % state.humi_threshold,
            alignment=QtCore.Qt.AlignRight,
            maximumWidth=36,
        )
        self.qlin_humi_threshold.editingFinished.connect(
            self.process_qlin_humi_threshold
        )
        self.qpbt_open_when_super_humi = QtWid.QPushButton(
            (
                "humidity > threshold"
                if state.open_valve_when_super_humi
                else "humidity < threshold"
            ),
            checkable=True,
            checked=state.open_valve_when_super_humi,
        )
        self.qpbt_open_when_super_humi.clicked.connect(
            self.process_qpbt_open_when_super_humi
        )

        # fmt: off
        grid = QtWid.QGridLayout()
        grid.addWidget(QtWid.QLabel("Is valve open?")    , 0, 0)
        grid.addWidget(self.LED_is_valve_open            , 0, 1)
        grid.addWidget(QtWid.QLabel("Humidity threshold"), 1, 0)
        grid.addWidget(self.qlin_humi_threshold          , 1, 1)
        grid.addWidget(QtWid.QLabel("%")                 , 1, 2)
        grid.addWidget(QtWid.QLabel("Open valve when")   , 2, 0)
        grid.addWidget(self.qpbt_open_when_super_humi    , 2, 1, 1, 2)
        grid.setAlignment(QtCore.Qt.AlignTop)
        # fmt: on

        qgrp_valve = QtWid.QGroupBox("Valve control")
        qgrp_valve.setLayout(grid)

        # Round up right frame
        vbox = QtWid.QVBoxLayout()
        vbox.addWidget(qgrp_readings)
        vbox.addWidget(qgrp_comments)
        vbox.addWidget(qgrp_valve)  # , alignment=QtCore.Qt.AlignLeft)
        vbox.addWidget(qgrp_chart, alignment=QtCore.Qt.AlignLeft)
        vbox.addStretch()

        # Round up bottom frame
        hbox_bot = QtWid.QHBoxLayout()
        hbox_bot.addWidget(self.gw, 1)
        hbox_bot.addLayout(vbox, 0)

        # -------------------------
        #   Round up full window
        # -------------------------

        vbox = QtWid.QVBoxLayout(self)
        vbox.addLayout(hbox_top, stretch=0)
        vbox.addSpacerItem(QtWid.QSpacerItem(0, 10))
        vbox.addLayout(hbox_bot, stretch=1)

    # --------------------------------------------------------------------------
    #   Handle controls
    # --------------------------------------------------------------------------

    @QtCore.pyqtSlot()
    def process_qlin_humi_threshold(self):
        try:
            humi_threshold = float(self.qlin_humi_threshold.text())
        except (TypeError, ValueError):
            humi_threshold = 50
        except:
            raise

        state.humi_threshold = np.clip(humi_threshold, 0, 100)
        self.qlin_humi_threshold.setText("%.0f" % state.humi_threshold)
        qdev_ard.send(ard.write, "th%.0f" % state.humi_threshold)

    @QtCore.pyqtSlot()
    def process_qpbt_open_when_super_humi(self):
        if self.qpbt_open_when_super_humi.isChecked():
            state.open_valve_when_super_humi = True
            self.qpbt_open_when_super_humi.setText("humidity > threshold")
            qdev_ard.send(ard.write, "open when super humi")

        else:
            state.open_valve_when_super_humi = False
            self.qpbt_open_when_super_humi.setText("humidity < threshold")
            qdev_ard.send(ard.write, "open when sub humi")

    @QtCore.pyqtSlot()
    def update_GUI(self):
        str_cur_date, str_cur_time, _ = get_current_date_time()
        self.qlbl_cur_date_time.setText(
            "%s    %s" % (str_cur_date, str_cur_time)
        )
        self.qlbl_update_counter.setText("%i" % qdev_ard.update_counter_DAQ)
        self.qlbl_DAQ_rate.setText(
            "DAQ: %.1f Hz" % qdev_ard.obtained_DAQ_rate_Hz
        )
        if log.is_recording():
            self.qlbl_recording_time.setText(log.pretty_elapsed())

        self.qlin_ds18b20_temp.setText("%.1f" % state.ds18b20_temp)
        self.qlin_dht22_temp.setText("%.1f" % state.dht22_temp)
        self.qlin_dht22_humi.setText("%.1f" % state.dht22_humi)
        self.qlbl_title.setText(
            "Interior:  %.1f °C,  %.1f %%"
            % (state.dht22_temp, state.dht22_humi)
        )

        if state.is_valve_open:
            self.LED_is_valve_open.setText("1")
            self.LED_is_valve_open.setChecked(True)
        else:
            self.LED_is_valve_open.setText("0")
            self.LED_is_valve_open.setChecked(False)

    @QtCore.pyqtSlot()
    def update_chart(self):
        if DEBUG:
            tprint("update_chart")

        for tscurve in self.tscurves:
            tscurve.update()

# ------------------------------------------------------------------------------
#   Program termination routines
# ------------------------------------------------------------------------------

def stop_running():
    app.processEvents()
    qdev_ard.quit()
    log.close()

    print("Stopping timers................ ", end="")
    timer_GUI.stop()
    timer_charts.stop()
    print("done.")

@QtCore.pyqtSlot()
def notify_connection_lost():
    stop_running()

    window.qlbl_title.setText("! ! !    LOST CONNECTION    ! ! !")
    str_cur_date, str_cur_time, _ = get_current_date_time()
    str_msg = "%s %s\nLost connection to Arduino." % (
        str_cur_date,
        str_cur_time,
    )
    print("\nCRITICAL ERROR @ %s" % str_msg)
    reply_ = QtWid.QMessageBox.warning(
        window, "CRITICAL ERROR", str_msg, QtWid.QMessageBox.Ok
    )

    if reply_ == QtWid.QMessageBox.Ok:
        pass  # Leave the GUI open for read-only inspection by the user

@QtCore.pyqtSlot()
def about_to_quit():
    print("\nAbout to quit")
    stop_running()
    ard.close()

# ------------------------------------------------------------------------------
#   Your Arduino update function
# ------------------------------------------------------------------------------

def DAQ_function():
    # Date-time keeping
    str_cur_date, str_cur_time, str_cur_datetime = get_current_date_time()

    # Query the Arduino for its state
    success_, tmp_state = ard.query_ascii_values("?", delimiter="\t")
    if not (success_):
        dprint(
            "'%s' reports IOError @ %s %s"
            % (ard.name, str_cur_date, str_cur_time)
        )
        return False

    # Parse readings into separate state variables
    try:
        (
            state.time,
            state.ds18b20_temp,
            state.dht22_temp,
            state.dht22_humi,
            state.is_valve_open,
        ) = tmp_state
        state.time /= 1000  # Arduino time, [msec] to [s]
        state.is_valve_open = bool(state.is_valve_open)
    except Exception as err:
        pft(err, 3)
        dprint(
            "'%s' reports IOError @ %s %s"
            % (ard.name, str_cur_date, str_cur_time)
        )
        return False

    # We will use PC time instead
    state.time = time.perf_counter()

    # Add readings to chart histories
    window.tscurve_ds18b20_temp.appendData(state.time, state.ds18b20_temp)
    window.tscurve_dht22_temp.appendData(state.time, state.dht22_temp)
    window.tscurve_dht22_humi.appendData(state.time, state.dht22_humi)

    # Logging to file
    log.update(filepath=str_cur_datetime + ".txt", mode="w")

    # Return success
    return True

def write_header_to_log():
    log.write("[HEADER]\n")
    log.write(window.qtxt_comments.toPlainText())
    log.write("\n\n[DATA]\n")
    log.write("time\tDS18B20 temp.\tDHT22 temp.\tDHT22 humi.\tvalve\n")
    log.write("[s]\t[±0.5 °C]\t[±0.5 °C]\t[±3 pct]\t[0/1]\n")

def write_data_to_log():
    log.write(
        "%.1f\t%.1f\t%.1f\t%.1f\t%i\n"
        % (
            log.elapsed(),
            state.ds18b20_temp,
            state.dht22_temp,
            state.dht22_humi,
            state.is_valve_open,
        )
    )

# ------------------------------------------------------------------------------
#   Main
# ------------------------------------------------------------------------------

if __name__ == "__main__":
    # Set priority of this process to maximum in the operating system
    print("PID: %s\n" % os.getpid())
    try:
        proc = psutil.Process(os.getpid())
        if os.name == "nt":
            proc.nice(psutil.REALTIME_PRIORITY_CLASS)  # Windows
        else:
            proc.nice(-20)  # Other
    except:
        print("Warning: Could not set process to maximum priority.\n")

    # --------------------------------------------------------------------------
    #   Connect to Arduino
    # --------------------------------------------------------------------------

    ard = Arduino(name="Ard", connect_to_specific_ID="Ambre chamber")
    ard.serial_settings["baudrate"] = 115200
    ard.auto_connect()

    if not (ard.is_alive):
        print("\nCheck connection and try resetting the Arduino.")
        print("Exiting...\n")
        sys.exit(0)

    # Get the initial state of the valve control
    success, reply = ard.query("th?")
    if success:
        state.humi_threshold = float(reply)

    success, reply = ard.query("open when super humi?")
    if success:
        state.open_valve_when_super_humi = bool(int(reply))

    # --------------------------------------------------------------------------
    #   Create application and main window
    # --------------------------------------------------------------------------
    QtCore.QThread.currentThread().setObjectName("MAIN")  # For DEBUG info

    app = QtWid.QApplication(sys.argv)
    app.aboutToQuit.connect(about_to_quit)

    window = MainWindow()

    # --------------------------------------------------------------------------
    #   File logger
    # --------------------------------------------------------------------------

    log = FileLogger(
        write_header_function=write_header_to_log,
        write_data_function=write_data_to_log,
    )
    log.signal_recording_started.connect(
        lambda filepath: window.qpbt_record.setText(
            "Recording to file: %s" % filepath
        )
    )
    log.signal_recording_stopped.connect(
        lambda: window.qpbt_record.setText("Click to start recording to file")
    )

    # --------------------------------------------------------------------------
    #   Set up multithreaded communication with the Arduino
    # --------------------------------------------------------------------------

    # Create QDeviceIO
    qdev_ard = QDeviceIO(ard)

    # Create workers
    # fmt: off
    qdev_ard.create_worker_DAQ(
        DAQ_function             = DAQ_function,
        DAQ_interval_ms          = DAQ_INTERVAL_MS,
        critical_not_alive_count = 1,
        debug                    = DEBUG,
    )
    # fmt: on
    qdev_ard.create_worker_jobs()

    # Connect signals to slots
    qdev_ard.signal_DAQ_updated.connect(window.update_GUI)
    qdev_ard.signal_connection_lost.connect(notify_connection_lost)

    # Start workers
    qdev_ard.start(DAQ_priority=QtCore.QThread.TimeCriticalPriority)

    # --------------------------------------------------------------------------
    #   Timers
    # --------------------------------------------------------------------------

    timer_GUI = QtCore.QTimer()
    timer_GUI.timeout.connect(window.update_GUI)
    timer_GUI.start(100)

    timer_charts = QtCore.QTimer()
    timer_charts.timeout.connect(window.update_chart)
    timer_charts.start(CHART_INTERVAL_MS)

    # --------------------------------------------------------------------------
    #   Start the main GUI event loop
    # --------------------------------------------------------------------------

    window.show()
    sys.exit(app.exec_())


import pygame
import numpy as np
from util.helpers import *
from physics.colliding_object import Colliding

class EyeBeam(Colliding):
    def __init__(self, start, end):
        self.start = np.array(start)
        super(EyeBeam, self).__init__(self.start)
        self.end = np.array(end)
        self.collide_type = 'line'

    def unobstructed(self, list_of_game_objects):
        walls_vector = walls_vector_from_game_objects(list_of_game_objects)
        edge_vector = np.array((self.start, self.end))
        return unobstructed_edges(edge_vector, walls_vector)[0]

class Eyes:
    def __init__(self, view_distance=200):
        self.view_distance = view_distance
        self.look_ahead = 10
        self.list_of_game_objects = []

    def update(self, list_of_game_objects):
        self.list_of_game_objects = list_of_game_objects

    def direct_path_to_goal(self, current_position, goal, exclude=[]):
        obstructions = [i for i in self.list_of_game_objects if i not in exclude]
        walls_vector = walls_vector_from_game_objects(obstructions)
        if len(walls_vector) == 0:
            return True
        goal_edge = np.array([[current_position[0], current_position[1], goal[0], goal[1]]])
        return unobstructed_edges(goal_edge, walls_vector)

    def get_mouse_position(self):
        return np.array(pygame.mouse.get_pos()).astype(float)

    def look_for_collisions(self, coords, vector, radius):
        for sign in [1.0, 0.0, -1.0]:
            adjustment = normalise_vector(perpendicular_vector(vector)) * (sign * radius)
            adjusted_coords = coords + adjustment
            ahead_end = adjusted_coords + (vector * self.look_ahead)
            ahead = EyeBeam(adjusted_coords, ahead_end)
            collision = ahead.get_closest_collision(self.list_of_game_objects)
            if collision is not None:
                return collision
        return None

    def look_at_object(self, coords, screen_object):
        if self.direct_path_to_goal(coords, screen_object.coords(), exclude=[screen_object]):
            return screen_object
        else:
            return None

    def visible_objects(self, coords):
        visibles = []
        for screen_object in self.list_of_game_objects:
            eyes_see = self.look_at_object(coords,
                                           screen_object)
            if eyes_see is not None:
                visibles.append(eyes_see)
        return visibles

    def look_for_object(self,
                        coords,
                        object_description):
        matching_objects_in_range = [screen_object for screen_object in \
                            self.list_of_game_objects \
                            if screen_object.image['kind'] != 'wall'
                            and distance_between_points(coords, screen_object.coords()) < self.view_distance \
                            and object_description.viewitems() <= screen_object.image.viewitems()]
        if len(matching_objects_in_range) > 0:
            closest_index = find_closest_point_index(coords, [screen_object.coords() for screen_object in matching_objects_in_range])
            target_object = matching_objects_in_range[closest_index]
            return self.look_at_object(coords, target_object)
        return None


from core.visualization import PlotGraphics
from core.relation_extraction import SpacyRelationExtraction
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report
from sklearn.metrics import roc_auc_score
from sklearn.metrics import f1_score
import numpy as np
import joblib
import os
import sys
import warnings

warnings.filterwarnings('ignore')

class TextPipeline:

    def __init__(self, predictor_name, data_tuple, classifier_name='decision_tree', resources_path=None):
        # root path
        self.resources_folder = os.path.join(os.path.dirname(sys.path[0]), 'resources') \
            if resources_path is None else resources_path
        # initializes the classifier dict
        classifiers = {'decision_tree': DecisionTreeClassifier(random_state=0),
                       'k_neighbors': KNeighborsClassifier(n_neighbors=15)}
        # save the predictor name
        self.predictor_name = predictor_name

        # receives the data
        self.x_train, self.x_test, self.y_train, self.y_test = data_tuple
        # text extraction pipelines
        self.text_extraction_pipes = {}
        # prediction model to use
        self.prediction_model = classifiers[classifier_name]
        # init the visualizer
        self.plt_graphics = PlotGraphics(data_tuple, self.text_extraction_pipes)

    def create_features_pipeline(self, n_features=150, pipeline_obj=None):
        # checks if the transformer
        if pipeline_obj is None:
            # features vectorization
            transformer_obj = TfidfVectorizer(strip_accents='unicode',
                                              stop_words='english',
                                              lowercase=True,
                                              max_features=n_features,
                                              ngram_range=(1, 2),
                                              min_df=0.1, max_df=0.7)
            # creates the pipeline obj
            pipeline_obj = Pipeline([('vectorizer', transformer_obj)])
        # pipeline mapping
        self.text_extraction_pipes['feature'] = pipeline_obj
        # returns the pipeline obj
        return self.text_extraction_pipes['feature']

    def create_label_pipeline(self, relation_extraction=False, n_targets=20, n_jobs=8):
        # target vectorization
        if relation_extraction:
            # uses the spacy relation extraction
            vectorizer = SpacyRelationExtraction(n_relation=n_targets, n_jobs=n_jobs)
        else:
            # otherwise uses a normal vectorizer
            vectorizer = CountVectorizer(strip_accents='unicode',
                                         stop_words='english',
                                         lowercase=True,
                                         max_features=n_targets,
                                         ngram_range=(1, 2),
                                         min_df=0.1, max_df=0.7)
        # pipeline creation
        self.text_extraction_pipes['target'] = Pipeline([('vectorizer', vectorizer)])

    def pickle_predictor(self):
        # save the labels pipeline
        labels_extractor = self.text_extraction_pipes['target']['vectorizer']
        obj_name = os.path.join(self.resources_folder, '_'.join([self.predictor_name, 'labels', 'vectorizer']))
        joblib.dump(labels_extractor, obj_name + '.pkl')

        # saves the model
        obj_name = os.path.join(self.resources_folder, '_'.join([self.predictor_name, 'predictor']))
        joblib.dump(self.prediction_model, obj_name + '.pkl')

    def unpickle_predictor(self):
        # loads the object
        obj_name = os.path.join(self.resources_folder, '_'.join([self.predictor_name, 'labels', 'vectorizer']))
        labels_extractor = joblib.load(obj_name + '.pkl')
        self.text_extraction_pipes['target'] = Pipeline([('vectorizer', labels_extractor)])

        # unpickle the model
        obj_name = os.path.join(self.resources_folder, '_'.join([self.predictor_name, 'predictor']))
        self.prediction_model = joblib.load(obj_name + '.pkl')

    def fit(self, x_vector):
        # fit the feature data
        y_vector = self.text_extraction_pipes['target'].fit_transform(self.y_train).toarray()
        # convert the y_train
        y_vector[y_vector > 1] = 1
        # print some information data
        print('\ninput array, shape:', x_vector.shape)
        print('output array, shape:', y_vector.shape, '\n')

        # fit the model
        self.prediction_model.fit(x_vector, y_vector)

    def predict(self, x_test):
        # convert using the pipeline
        x_test_vector = self.text_extraction_pipes['feature'].transform(x_test)
        # convert the y_test
        predictions = self.prediction_model.predict(x_test_vector)
        # returns the predictions
        return predictions

    def score(self):
        # add the exception treatment
        y_test_vector = self.text_extraction_pipes['target'].transform(self.y_test).toarray()
        # predict the output for the test set
        predictions = self.predict(self.x_test)

        # print some metrics
        class_labels = self.text_extraction_pipes['target']['vectorizer'].get_feature_names()
        class_report = self.calculate_metrics(y_test_vector, predictions, class_labels)
        # plot the data
        self.plt_graphics.plot_bag_words(class_report)

        # return the classification report
        return class_report

    @staticmethod
    def calculate_metrics(y_test, predictions, class_labels):
        # print the results
        y_test[y_test > 1] = 1
        class_report = classification_report(y_test, predictions, target_names=class_labels, output_dict=True)
        print("Classification report: \n", classification_report(y_test, predictions, target_names=class_labels))
        # print("F1 micro averaging:", f1_score(y_test, predictions, average='micro', labels=np.unique(predictions)))
        print("ROC: ", roc_auc_score(y_test, predictions), '\n')
        # return the classification results
        return class_report


from . animation import Animation
from .. layout import circle
from .. util import deprecated

class Circle(Animation):
    LAYOUT_CLASS = circle.Circle
    LAYOUT_ARGS = 'rings',

    def __init__(self, layout, **kwds):
        super().__init__(layout, **kwds)
        self.rings = layout.rings
        self.ringCount = layout.ringCount
        if deprecated.allowed():  # pragma: no cover
            self.lastRing = layout.lastRing
        self.ringSteps = layout.ringSteps

if deprecated.allowed():  # pragma: no cover
    BaseCircleAnim = Circle


"""
Given a universal mesh, record the placements of guide nodes as it relative to
universal mesh. And then repoisition guides to that relative position should
the universal mesh change from character to character.

from mgear.shifter import relativeGuidePlacement
reload(relativeGuidePlacement)

Execute the following chunk to record initial placement ----------------------
relativeGuidePlacement.exportGuidePlacement(filepath="Y:/tmp/exampleFile.json",
                                            skip_strings=["hair"])

Load new universal guide mesh with new proportions

Execute the following lines to move the guides to their new position ---------
relativeGuidePlacement.importGuidePlacement(filepath="Y:/tmp/exampleFile.json")

Attributes:
    GUIDE_ROOT (str): name of the root guide node
    SKIP_CONTAINS (list): nodes to skip if they contain the string
    SKIP_CRAWL_NODES (list): nodes to skip crawling hierarchy
    SKIP_NODETYPES (list): skip the query of certain node types
    SKIP_PLACEMENT_NODES (TYPE): nodes to skip updating their positions
    SKIP_SUFFIX (list): skip if node ends with
    UNIVERSAL_MESH_NAME (str): default name of the universal mesh
"""
# python
import json
import math

# dcc
import maya.cmds as mc
import pymel.core as pm
import maya.OpenMaya as om

# mgear
from mgear.core import utils
from mgear.core import vector
from mgear.core import transform
from mgear.core import meshNavigation

# constants -------------------------------------------------------------------

# Designate the root of the hierarchy to crawl
GUIDE_ROOT = "guide"

# Nodes to avoid checking the hierarchy
DEFAULT_SKIP_CRAWL_NODES = ("controllers_org",
                            "spineUI_C0_root",
                            "faceUI_C0_root",
                            "legUI_R0_root",
                            "armUI_L0_root",
                            "legUI_L0_root",
                            "armUI_R0_root")

# nodes that will not have their positions updated
DEFAULT_SKIP_PLACEMENT_NODES = ("controllers_org",
                                "global_C0_root",
                                "spineUI_C0_root",
                                "faceUI_C0_root",
                                "legUI_R0_root",
                                "armUI_L0_root",
                                "legUI_L0_root",
                                "armUI_R0_root")

try:
    SKIP_CRAWL_NODES
    SKIP_PLACEMENT_NODES
except NameError:
    SKIP_CRAWL_NODES = list(DEFAULT_SKIP_CRAWL_NODES)
    SKIP_PLACEMENT_NODES = list(DEFAULT_SKIP_PLACEMENT_NODES)

# skip the node if it even contains the characters in the list
# eg SKIP_CONTAINS = ["hair"]
SKIP_CONTAINS = []

# Avoid nodes of a specified suffix
SKIP_SUFFIX = ["sizeRef", "crv", "crvRef", "blade"]

# Types of nodes to avoid
SKIP_NODETYPES = ["aimConstraint", "pointConstraint", "parentConstraint"]

UNIVERSAL_MESH_NAME = "skin_geo_setup"

# general functions -----------------------------------------------------------

def crawlHierarchy(parentNode,
                   ordered_hierarchy,
                   skip_crawl_nodes,
                   skip_strings=None):
    """recursive function to crawl a hierarchy of nodes to return decendents

    Args:
        parentNode (str): node to query
        ordered_hierarchy (str): list to continuesly pass itself
        skip_crawl_nodes (list): nodes to skip crawl
    """
    if not skip_strings:
        skip_strings = []
    for node in mc.listRelatives(parentNode, type="transform") or []:
        if node in skip_crawl_nodes or node in ordered_hierarchy:
            continue
        if node.endswith(tuple(SKIP_SUFFIX)):
            continue
        if mc.objectType(node) in SKIP_NODETYPES:
            continue
        if [True for skip_str in skip_strings
                if skip_str.lower() in node.lower()]:
            continue
        ordered_hierarchy.append(node)
        crawlHierarchy(node,
                       ordered_hierarchy,
                       skip_crawl_nodes,
                       skip_strings=skip_strings)

def getPostionFromLoop(vertList):
    """Get the center position from the list of edge ids provided

    Args:
        vertList (list): list of edge ids

    Returns:
        list: of translate XYZ, world space
    """
    bb = mc.exactWorldBoundingBox(vertList)
    pos = ((bb[0] + bb[3]) / 2, (bb[1] + bb[4]) / 2, (bb[2] + bb[5]) / 2)
    return pos

def getVertMatrix(closestVert):
    """create a matrix from the closestVert and the normals of the surrounding
    faces for later comparison

    Args:
        node (str): guide node to query
        closestVert (str): closest vert to guide

    Returns:
        list: of matrices
    """
    closestVert = pm.PyNode(closestVert)
    faces = closestVert.connectedFaces()
    normalVector = faces.getNormal("world")
    pm.select(faces)
    faces_str = mc.ls(sl=True, fl=True)
    pm.select(cl=True)
    face_pos = pm.dt.Vector(getPostionFromLoop(faces_str))
    normal_rot = getOrient([normalVector.x, normalVector.y, normalVector.z],
                           [0, 1, 0],
                           ro=0)
    orig_ref_matrix = pm.dt.TransformationMatrix()
    orig_ref_matrix.setTranslation(face_pos, pm.dt.Space.kWorld)
    orig_ref_matrix.setRotation(normal_rot)

    return orig_ref_matrix

def getOrient(normal, tangent, ro=0):
    """convert normal direction into euler rotations

    Args:
        normal (list): of nomel values
        ro (int, optional): rotate order

    Returns:
        list: of euler rotations
    """
    kRotateOrders = [om.MEulerRotation.kXYZ, om.MEulerRotation.kYZX,
                     om.MEulerRotation.kZXY, om.MEulerRotation.kXZY,
                     om.MEulerRotation.kYXZ, om.MEulerRotation.kZYX, ]
    cross = [normal[1] * tangent[2] - normal[2] * tangent[1],
             normal[2] * tangent[0] - normal[0] * tangent[2],
             normal[0] * tangent[1] - normal[1] * tangent[0]]
    tMatrix = normal + [0] + tangent + [0] + cross + [0, 0, 0, 0, 1]
    mMatrix = om.MMatrix()
    om.MScriptUtil.createMatrixFromList(tMatrix, mMatrix)
    tmMatrix = om.MTransformationMatrix(mMatrix)
    rotate = tmMatrix.eulerRotation().reorder(kRotateOrders[ro])
    RAD_to_DEG = (180 / math.pi)
    return [rotate[0] * RAD_to_DEG,
            rotate[1] * RAD_to_DEG,
            rotate[2] * RAD_to_DEG]

def getRepositionMatrix(node_matrix,
                        orig_ref_matrix,
                        mr_orig_ref_matrix,
                        closestVerts):
    """Get the delta matrix from the original position and multiply by the
    new vert position. Add the rotations from the face normals.

    Args:
        node_matrix (pm.dt.Matrix): matrix of the guide
        orig_ref_matrix (pm.dt.Matrix): matrix from the original vert position
        closestVerts (str): name of the closest vert

    Returns:
        mmatrix: matrix of the new offset position, worldSpace
    """
    current_vert = pm.PyNode(closestVerts[0])
    mr_current_vert = pm.PyNode(closestVerts[1])
    current_length = vector.getDistance(current_vert.getPosition("world"),
                                        mr_current_vert.getPosition("world"))

    orig_length = vector.getDistance(orig_ref_matrix.translate,
                                     mr_orig_ref_matrix.translate)
    orig_center = vector.linearlyInterpolate(orig_ref_matrix.translate,
                                             mr_orig_ref_matrix.translate)
    orig_center_matrix = pm.dt.Matrix()
    # orig_center_matrix.setTranslation(orig_center, pm.dt.Space.kWorld)
    orig_center_matrix = transform.setMatrixPosition(
        orig_center_matrix, orig_center)

    current_center = vector.linearlyInterpolate(
        current_vert.getPosition("world"),
        mr_current_vert.getPosition("world"))

    length_percentage = 1
    if current_length != 0 or orig_length != 0:
        length_percentage = current_length / orig_length
    # refPosition_matrix = pm.dt.TransformationMatrix()
    refPosition_matrix = pm.dt.Matrix()
    # refPosition_matrix.setTranslation(current_center, pm.dt.Space.kWorld)
    refPosition_matrix = transform.setMatrixPosition(
        refPosition_matrix, current_center)
    deltaMatrix = node_matrix * orig_center_matrix.inverse()
    deltaMatrix = deltaMatrix * length_percentage
    deltaMatrix = transform.setMatrixScale(deltaMatrix)
    refPosition_matrix = deltaMatrix * refPosition_matrix

    return refPosition_matrix

def getRepositionMatrixSingleRef(node_matrix,
                                 orig_ref_matrix,
                                 mr_orig_ref_matrix,
                                 closestVerts):
    """Get the delta matrix from the original position and multiply by the
    new vert position. Add the rotations from the face normals.

    Args:
        node_matrix (pm.dt.Matrix): matrix of the guide
        orig_ref_matrix (pm.dt.Matrix): matrix from the original vert position
        closestVerts (str): name of the closest vert

    Returns:
        mmatrix: matrix of the new offset position, worldSpace
    """
    closestVerts = pm.PyNode(closestVerts[0])
    faces = closestVerts.connectedFaces()
    normalVector = faces.getNormal("world")
    pm.select(faces)
    faces_str = mc.ls(sl=True, fl=True)
    pm.select(cl=True)
    face_pos = pm.dt.Vector(getPostionFromLoop(faces_str))
    normal_rot = getOrient([normalVector.x, normalVector.y, normalVector.z],
                           [0, 1, 0],
                           ro=0)
    refPosition_matrix = pm.dt.TransformationMatrix()
    refPosition_matrix.setTranslation(face_pos, pm.dt.Space.kWorld)
    refPosition_matrix.setRotation(normal_rot)

    deltaMatrix = node_matrix * orig_ref_matrix.inverse()
    refPosition_matrix = deltaMatrix * refPosition_matrix

    return refPosition_matrix

@utils.viewport_off
@utils.one_undo
def getGuideRelativeDictionaryLegacy(mesh, guideOrder):
    """create a dictionary of guide:[[shape.vtx[int]], relativeMatrix]

    Args:
        mesh (string): name of the mesh
        guideOrder (list): the order to query the guide hierarchy

    Returns:
        dictionary: create a dictionary of guide:[[edgeIDs], relativeMatrix]
    """
    relativeGuide_dict = {}
    mesh = pm.PyNode(mesh)
    for guide in guideOrder:
        guide = pm.PyNode(guide)
        # slow function A
        clst_vert = meshNavigation.getClosestVertexFromTransform(mesh, guide)
        vertexIds = [clst_vert.name()]
        # slow function B
        orig_ref_matrix = getVertMatrix(clst_vert.name())
        #  --------------------------------------------------------------------
        a_mat = guide.getMatrix(worldSpace=True)

        mm = ((orig_ref_matrix - a_mat) * -1) + a_mat
        pos = mm[3][:3]

        mr_vert = meshNavigation.getClosestVertexFromTransform(mesh, pos)
        mr_orig_ref_matrix = getVertMatrix(mr_vert.name())
        vertexIds.append(mr_vert.name())

        node_matrix = guide.getMatrix(worldSpace=True)
        relativeGuide_dict[guide.name()] = [vertexIds,
                                            node_matrix.get(),
                                            orig_ref_matrix.get(),
                                            mr_orig_ref_matrix.get()]
    mc.select(cl=True)
    return relativeGuide_dict

@utils.viewport_off
@utils.one_undo
def yieldGuideRelativeDictionary(mesh, guideOrder, relativeGuide_dict):
    """create a dictionary of guide:[[shape.vtx[int]], relativeMatrix]

    Args:
        mesh (string): name of the mesh
        guideOrder (list): the order to query the guide hierarchy

    Returns:
        dictionary: create a dictionary of guide:[[edgeIDs], relativeMatrix]
    """
    for guide in guideOrder:
        guide = pm.PyNode(guide)
        # slow function A
        clst_vert = meshNavigation.getClosestVertexFromTransform(mesh, guide)
        vertexIds = [clst_vert.name()]
        # slow function B
        orig_ref_matrix = getVertMatrix(clst_vert.name())
        #  --------------------------------------------------------------------
        a_mat = guide.getMatrix(worldSpace=True)

        mm = ((orig_ref_matrix - a_mat) * -1) + a_mat
        pos = mm[3][:3]

        mr_vert = meshNavigation.getClosestVertexFromTransform(mesh, pos)
        mr_orig_ref_matrix = getVertMatrix(mr_vert.name())
        vertexIds.append(mr_vert.name())

        node_matrix = guide.getMatrix(worldSpace=True)
        relativeGuide_dict[guide.name()] = [vertexIds,
                                            node_matrix.get(),
                                            orig_ref_matrix.get(),
                                            mr_orig_ref_matrix.get()]
        yield relativeGuide_dict

@utils.viewport_off
@utils.one_undo
def getGuideRelativeDictionary(mesh, guideOrder):
    """create a dictionary of guide:[[shape.vtx[int]], relativeMatrix]

    Args:
        mesh (string): name of the mesh
        guideOrder (list): the order to query the guide hierarchy

    Returns:
        dictionary: create a dictionary of guide:[[edgeIDs], relativeMatrix]
    """
    relativeGuide_dict = {}
    mesh = pm.PyNode(mesh)
    for result in yieldGuideRelativeDictionary(
            mesh, guideOrder, relativeGuide_dict):
        pass
    return relativeGuide_dict

@utils.viewport_off
@utils.one_undo
def updateGuidePlacementLegacy(guideOrder, guideDictionary):
    """update the guides based on new universal mesh, in the provided order

    Args:
        guideOrder (list): of the hierarchy to crawl
        guideDictionary (dictionary): dict of the guide:edge, matrix position
    """
    for guide in guideOrder:
        if guide not in guideDictionary or not mc.objExists(guide):
            continue
        elif guide in SKIP_PLACEMENT_NODES:
            continue
        (vertexIds,
         node_matrix,
         orig_ref_matrix,
         mr_orig_ref_matrix) = guideDictionary[guide]

        guideNode = pm.PyNode(guide)
        repoMatrix = getRepositionMatrix(pm.dt.Matrix(node_matrix),
                                         pm.dt.Matrix(orig_ref_matrix),
                                         pm.dt.Matrix(mr_orig_ref_matrix),
                                         vertexIds)
        guideNode.setMatrix(repoMatrix, worldSpace=True, preserve=True)

@utils.viewport_off
@utils.one_undo
def yieldUpdateGuidePlacement(guideOrder, guideDictionary):
    """update the guides based on new universal mesh, in the provided order

    Args:
        guideOrder (list): of the hierarchy to crawl
        guideDictionary (dictionary): dict of the guide:edge, matrix position
    """
    for guide in guideOrder:
        if guide not in guideDictionary or not mc.objExists(guide):
            continue
        elif guide in SKIP_PLACEMENT_NODES:
            continue
        (vertexIds,
         node_matrix,
         orig_ref_matrix,
         mr_orig_ref_matrix) = guideDictionary[guide]

        repoMatrix = getRepositionMatrix(pm.dt.Matrix(node_matrix),
                                         pm.dt.Matrix(orig_ref_matrix),
                                         pm.dt.Matrix(mr_orig_ref_matrix),
                                         vertexIds)
        yield repoMatrix

@utils.viewport_off
@utils.one_undo
def updateGuidePlacement(guideOrder, guideDictionary, reset_scale=False):
    """update the guides based on new universal mesh, in the provided order

    Args:
        guideOrder (list): of the hierarchy to crawl
        guideDictionary (dictionary): dict of the guide:edge, matrix position
    """
    updateGen = yieldUpdateGuidePlacement(guideOrder, guideDictionary)
    for guide in guideOrder:
        if guide not in guideDictionary or not mc.objExists(guide):
            continue
        elif guide in SKIP_PLACEMENT_NODES:
            continue
        guideNode = pm.PyNode(guide)
        scl = guideNode.getScale()
        repoMatrix = next(updateGen)
        guideNode.setMatrix(repoMatrix, worldSpace=True, preserve=True)
        if reset_scale:
            guideNode.setScale([1, 1, 1])
        else:
            guideNode.setScale(scl)
        yield True

# ==============================================================================
# Data export, still testing
# ==============================================================================
def _importData(filepath):
    try:
        with open(filepath, 'r') as f:
            data = json.load(f)
            return data
    except Exception as e:
        print(e)

def _exportData(data, filepath):
    try:
        with open(filepath, 'w') as f:
            json.dump(data, f, sort_keys=False, indent=4)
    except Exception as e:
        print(e)

def exportGuidePlacement(filepath=None,
                         reference_mesh=UNIVERSAL_MESH_NAME,
                         root_node=GUIDE_ROOT,
                         skip_crawl_nodes=SKIP_CRAWL_NODES,
                         skip_strings=[]):
    """Export the position of the supplied root node to a file.

    Args:
        filepath (str, optional): path to export too
        reference_mesh (str, optional): mesh to query verts
        root_node (str, optional): name of node to query against
        skip_crawl_nodes (list, optional): of nodes not to crawl
        skip_strings (list, optional): strings to check to skip node

    Returns:
        list: dict, list, str
    """
    if filepath is None:
        filepath = pm.fileDialog2(fileMode=0,
                                  startingDirectory="/",
                                  fileFilter="Export position(*.json)")
        if filepath:
            filepath = filepath[0]
    (relativeGuide_dict,
     ordered_hierarchy) = recordInitialGuidePlacement(
        reference_mesh=reference_mesh,
        root_node=root_node,
        skip_crawl_nodes=skip_crawl_nodes,
        skip_strings=skip_strings)
    data = {}
    data["relativeGuide_dict"] = relativeGuide_dict
    data["ordered_hierarchy"] = ordered_hierarchy
    _exportData(data, filepath)
    print("Guide position exported: {}".format(filepath))
    return relativeGuide_dict, ordered_hierarchy, filepath

@utils.one_undo
def importGuidePlacement(filepath):
    """import the position from the provided file

    Args:
        filepath (str): file to the json
        referenceMesh (str, optional): name of mesh to compare against
    """
    data = _importData(filepath)
    updateGuidePlacement(data["ordered_hierarchy"], data["relativeGuide_dict"])
    return data["relativeGuide_dict"], data["ordered_hierarchy"]

def recordInitialGuidePlacement(reference_mesh=UNIVERSAL_MESH_NAME,
                                root_node=GUIDE_ROOT,
                                skip_crawl_nodes=SKIP_CRAWL_NODES,
                                skip_strings=None):
    """convenience function for retrieving a dict of position

    Args:
        reference_mesh (str, optional): the mesh to query against
        root_node (str, optional): root node to crawl
        skip_crawl_nodes (list, optional): of nodes to avoid
        skip_strings (list, optional): of strings to check if skip

    Returns:
        dict, list: dict of positions, list of ordered nodes
    """
    ordered_hierarchy = []
    relativeGuide_dict = {}
    crawlHierarchy(root_node,
                   ordered_hierarchy,
                   skip_crawl_nodes,
                   skip_strings=skip_strings)
    relativeGuide_dict = getGuideRelativeDictionary(reference_mesh,
                                                    ordered_hierarchy)
    return relativeGuide_dict, ordered_hierarchy


from django.conf.urls import patterns, url

from ventas import viewsInforme,viewsPedido

urlpatterns = patterns('',
    url(r'^$', viewsPedido.venta_desktop, name='venta_desktop'),
    url(r'^fac/', viewsPedido.venta_desktop, name='venta_desktop1'),
    url(r'^mobile/$', viewsPedido.venta_mobile, name='venta_mobile'),
    url(r'^listar/$', viewsInforme.listar, name='listar'),
    url(r'^clientes/', viewsPedido.clientes, name='clientes'),
    url(r'^vendedores/', viewsPedido.vendedores, name='vendedores'),
    url(r'^codproducto/', viewsPedido.codproducto, name='codproducto'),
    url(r'^nomproducto/', viewsPedido.nomproducto, name='nomproducto'),
    url(r'^save/$', viewsPedido.savePedido, name='save'),
    url(r'^save/(?P<anyway>\w+)/$', viewsPedido.savePedido, name='save'),
    url(r'^saveDetalle/$', viewsPedido.saveDetalle, name='saveDetalle'),
    url(r'^deleteDetalle/(?P<id>\d+)/$', viewsPedido.deleteDetalle, name='deleteDetalle'),
    url(r'^pagar/$', viewsPedido.pagarPedido, name='pagarPedido'),
)


#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import logging; logger = logging.getLogger("main")
FORMAT = '%(asctime)s - %(levelname)s: %(message)s'
logging.basicConfig(format=FORMAT, level=logging.WARNING)

import time

from flask import Flask, escape, url_for,render_template, g, request, redirect, jsonify, session
from werkzeug import secure_filename

import sys, os
from jinja2 import Environment, PackageLoader
import json

app = Flask(__name__, static_folder='static')

maze = []
width = 0
height = 0

STARTPOS=[1,1]
MAXLIFE = 10
MIN_TIME_BETWEEN_INTERACTIONS=0.2 #seconds

robots = {}

def store_map(rawmaze):

    global maze, width, height

    if is_map_loaded():
        logger.warning("Map already loaded. Ignoring it. Restart the backend if you want to update the map.")
        return

    width = rawmaze["width"]
    height = rawmaze["height"]

    maze = [True if x in [399,431,463,492,493,494,495] else False for x in rawmaze["data"]]

    for j in range(height):
        for i in range(width):
            idx = i + j * width
            if maze[idx]:
                sys.stdout.write('.')
            else:
                sys.stdout.write(' ')
        sys.stdout.write('\n')

    logger.info("Maze successfully loaded!")

def is_map_loaded():
    return width and height

def get_obstacles(x,y):

    # obstacle at centre, north, south, east, west?
    obstacles = [True, True, True, True, True]

    if x >= 0 and y >= 0 and x < width and y < height and maze[x + y * width]:
        obstacles[0] = False

    if x >= 0 and y-1 >= 0 and x < width and y-1 < height and maze[x + (y-1) * width]:
        obstacles[1] = False

    if x >= 0 and y+1 >= 0 and x < width and y+1 < height and maze[x + (y+1) * width]:
        obstacles[2] = False

    if x+1 >= 0 and y >= 0 and x+1 < width and y < height and maze[x+1 + y * width]:
        obstacles[3] = False

    if x-1 >= 0 and y >= 0 and x-1 < width and y < height and maze[x-1 + y * width]:
        obstacles[4] = False

    logger.info(str(obstacles))
    return obstacles

@app.route("/set/<name>/<x>/<y>")
def set_robot(name, x, y):
    logger.info("Placing robot %s to %s,%s" % (name,x,y))

    x = int(x)
    y=int(y)
    c,_,_,_,_ = get_obstacles(x,y)
    if c:
        logger.info("Can not place robot there!")
        return json.dumps(False)

    robots[name]["pos"] = [x,y]
    return json.dumps(True)

def get_robot(name):
    if name not in robots:
        return json.dumps([-1,-1])
    return json.dumps(robots[name]["pos"])

@app.route("/")
def main():
    return render_template('index.html')

@app.route("/live")
def map():
    return render_template('map.html')

@app.route("/get_robots")
def get_robots():

    now = time.time()

    complete_robots = dict(robots)

    for k in list(robots.keys()):
        if robots[k]["life"] <= 0:
            logger.warning("Robot %s has no life left! killing it!" % k)
            del robots[k]
            del complete_robots[k]
            continue

        if now - robots[k]["lastinteraction"] > 60 * 10:
            logger.warning("Robot %s has not being used for 10 min. Removing it." % k)
            del robots[k]
            del complete_robots[k]
            continue

        complete_robots[k]["age"] = now - robots[k]["created"]

    return json.dumps(complete_robots)

def create_new_robot(name):
    logger.info("Placing new robot %s at start position" % name)
    robots[name] = {"pos": STARTPOS,
                    "created": time.time(),
                    "lastinteraction": 0,
                    "life": MAXLIFE
                    }

@app.route('/move/<name>/<direction>')
def move(name, direction):
    if not is_map_loaded():
        logger.error("Map not loaded yet! Reload webpage.")
        return json.dumps([False,[]])

    if name not in robots:
        create_new_robot(name)

    logger.info("Moving robot %s to %s" % (name,direction))

    now = time.time()
    if now - robots[name]["lastinteraction"] < MIN_TIME_BETWEEN_INTERACTIONS:
        logger.error("Too many interactions with %s. Wait a bit." % name)
        return json.dumps([False,[]])

    robots[name]["lastinteraction"] = now

    x,y = robots[name]["pos"]

    if direction == 'N':
        nx, ny = x, y-1
    if direction == 'S':
        nx, ny = x, y+1
    if direction == 'E':
        nx, ny = x+1, y
    if direction == 'W':
        nx, ny = x-1, y

    c,n,s,e,w = get_obstacles(nx,ny)

    if c:
        logger.info("...can not move there!")
        robots[name]["life"] -= 1
        return json.dumps([False,[]])

    else:
        robots[name]["pos"] = [nx,ny]
        return json.dumps([True,[n,s,e,w]])

@app.route("/map", methods=['POST'])
def load_map():
    logger.info("Retrieving the map data...")

    store_map(json.loads([k for k in request.form.keys()][0]))
    return ""

@app.route("/life/<name>")
def life(name):
    return json.dumps(robots[name]["life"] if name in robots else 0)


"""
GenT2MF_Trapezoidal.py
Created 3/1/2022
"""
from __future__ import annotations
from typing import List
from juzzyPython.generalType2zSlices.sets.GenT2MF_Prototype import GenT2MF_Prototype
from juzzyPython.intervalType2.sets.IntervalT2MF_Trapezoidal import IntervalT2MF_Trapezoidal
from juzzyPython.type1.sets.T1MF_Trapezoidal import T1MF_Trapezoidal

class GenT2MF_Trapezoidal(GenT2MF_Prototype):
    """
    Class GenT2MF_Trapezoidal
    Creates a new instance of GenT2zMF_Trapezoidal

    Parameters:
        primer
        primer0
        primer1
        primers
        numberOfzLevels

    Functions:
        getZSlice

    """

    def __init__(self, name: str,primer: IntervalT2MF_Trapezoidal = None,primer0: IntervalT2MF_Trapezoidal = None, primer1: IntervalT2MF_Trapezoidal = None,primers: List[IntervalT2MF_Trapezoidal] = None, numberOfzLevels = None) -> None:
        super().__init__(name)
        self.DEBUG = False
        if primer != None:
            stepsize = [0] * 4
            self.numberOfzLevels = numberOfzLevels
            self.support = primer.getSupport()
            self.primer = primer
            slices_fs = [0] * numberOfzLevels
            self.slices_zValues = [0] * numberOfzLevels

            z_stepSize = 1.0/numberOfzLevels
            self.zSlices = [0] * numberOfzLevels
            stepsize[0] = (primer.getLMF().getA() - primer.getUMF().getA())/(numberOfzLevels-1)/2.0
            stepsize[1] = (primer.getLMF().getB() - primer.getUMF().getB())/(numberOfzLevels-1)/2.0
            stepsize[2] = (primer.getUMF().getC() - primer.getLMF().getC())/(numberOfzLevels-1)/2.0
            stepsize[3] = (primer.getUMF().getD() - primer.getLMF().getD())/(numberOfzLevels-1)/2.0

            inner = primer.getLMF().getParameters().copy()
            outer = primer.getUMF().getParameters().copy()

            self.zSlices[0] = IntervalT2MF_Trapezoidal("Slice 0",primer.getUMF(),primer.getLMF())
            self.slices_zValues[0] = z_stepSize
            if self.DEBUG:
                print(self.zSlices[0].toString()+"  Z-Value = "+str(self.slices_zValues[0]))

            for i in range(1,numberOfzLevels):
                self.slices_zValues[i] = self.slices_zValues[i-1]+z_stepSize
                inner[0]-=stepsize[0]
                inner[1]-=stepsize[1]
                inner[2]+=stepsize[2]
                inner[3]+=stepsize[3]
                outer[0]+=stepsize[0]
                outer[1]+=stepsize[1]
                outer[2]-=stepsize[2]
                outer[3]-=stepsize[3]

                if(inner[0]<outer[0]):
                    inner[0] = outer[0]
                if(inner[1]<outer[1]):
                    inner[1] = outer[1]
                if(inner[2]>outer[2]):
                    inner[2] = outer[2]
                if(inner[3]>outer[3]):
                    inner[3] = outer[3]

                self.zSlices[i] = IntervalT2MF_Trapezoidal("Slice "+str(i), T1MF_Trapezoidal("upper_slice "+str(i),outer),T1MF_Trapezoidal("lower_slice "+str(i),inner))

                if self.DEBUG:
                    print(self.zSlices[i].toString()+"  Z-Value = "+str(self.slices_zValues[i]))

        elif primer0 != None and primer1 != None:
            if self.DEBUG:
                print("Number of zLevels: "+str(numberOfzLevels))

            self.numberOfzLevels = numberOfzLevels
            self.support = primer0.getSupport()
            slices_fs = [0] * numberOfzLevels
            self.slices_zValues = [0] * numberOfzLevels
            self.zSlices = [0] * numberOfzLevels

            self.zSlices[0] = primer0
            self.zSlices[0].setName(self.getName()+"_Slice_0")
            self.zSlices[-1] = primer1

            z_stepSize = 1.0/(numberOfzLevels)
            self.slices_zValues[0] = z_stepSize
            self.slices_zValues[-1] = 1.0

            lsu = (primer1.getUMF().getParameters()[0]-primer0.getUMF().getParameters()[0])/(numberOfzLevels-1)
            lsl = (primer0.getLMF().getParameters()[0]-primer1.getLMF().getParameters()[0])/(numberOfzLevels-1)

            rsu = (primer0.getUMF().getParameters()[3]-primer1.getUMF().getParameters()[3])/(numberOfzLevels-1)
            rsl = (primer1.getLMF().getParameters()[3]-primer0.getLMF().getParameters()[3])/(numberOfzLevels-1)

            if self.DEBUG:
                print("lsu = "+str(lsu)+"  lsl = "+str(lsl)+"  rsu = "+str(rsu)+"  rsl = "+str(rsl))

            inner = primer0.getLMF().getParameters().copy()
            outer = primer0.getUMF().getParameters().copy()

            for i in range(1,numberOfzLevels-1):
                self.slices_zValues[i] = self.slices_zValues[i-1]+z_stepSize
                inner[0]-=lsl
                inner[3]+=rsl
                outer[0]+=lsu
                outer[3]-=rsu

                if self.DEBUG:
                    print("Slice "+str(i)+" , inner: "+str(inner[0])+"  "+str(inner[1])+"  "+str(inner[2])+"   outer: "+str(outer[0])+"  "+str(outer[1])+"  "+str(outer[2]))
                self.zSlices[i] = IntervalT2MF_Trapezoidal(self.getName()+"_Slice_"+str(i),T1MF_Trapezoidal("upper_slice "+str(i),outer),T1MF_Trapezoidal("lower_slice "+str(i),inner))

                if self.DEBUG:
                    print(self.zSlices[i].toString()+"  Z-Value = "+str(self.slices_zValues[i]))

        elif primers != None:
            self.numberOfzLevels = len(primers)
            self.support = primers[0].getSupport()

            slices_fs = [0] * self.numberOfzLevels
            self.slices_zValues = [0] * self.numberOfzLevels
            z_stepSize = 1.0/self.numberOfzLevels
            self.slices_zValues[0] = z_stepSize
            self.zSlices = primers.copy()

            for i in range(self.numberOfzLevels):
                self.slices_zValues[i] = z_stepSize*(i+1)
                if self.DEBUG:
                    print(self.zSlices[i].toString()+"  Z-Value = "+str(self.slices_zValues[i]))

    def clone(self) -> GenT2MF_Trapezoidal:
        """Not implemented"""
        print("Not implemented")
        return None

    def getZSlice(self, slice_number: int) -> IntervalT2MF_Trapezoidal:
        """Return the slice number"""
        return self.zSlices[slice_number]

    def getLeftShoulderStart(self) -> float:
        """Not implemented"""
        print("Not implemented")
        return float("Nan")

    def getRightShoulderStart(self) -> float:
        """Not implemented"""
        print("Not implemented")
        return float("Nan")


from examples.example_imports import *

scene = EagerModeScene()

fixed_point = Sphere(radius=0.08).move_to(ORIGIN).set_color(GREEN_D)
scene.add(fixed_point)

start_rod = Vec3(*UP*3)
end_rod = Vec3(-3, 3, 0)
L = (end_rod - start_rod).norm()
# rod = Line3D(start_rod, end_rod, width=0.08).set_color(RED_D)
fine_line = Line3D(start_rod.to_array(), end_rod.to_array(), width=0.02).set_color(RED_D)
scene.add(fine_line)
massive_bob = Sphere(radius=0.12).move_to(end_rod.to_array()).set_color(GREY_D)
m = 1. #kg
g = 9.8
G = Vec3(0, -g, 0)# m/s^2
scene.add(massive_bob)

f = Vec3(0, 0, 0)
tension = Vec3(0, 0, 0)

f_arrow = Arrow(end_rod.to_array(), f.to_array(), buff=0)
mg_arrow = Arrow(end_rod.to_array(), m*G.to_array(), buff=0)
tension_arrow = Arrow(end_rod.to_array(), tension.to_array(), buff=0)
scene.add(f_arrow, mg_arrow, tension_arrow)

v = Vec3(0, 0, 0)

def update_func(obj, dt):
    global v, end_rod, fine_line, f, tension
    rob_vec = end_rod - start_rod
    theta = rob_vec.angle_between(DOWN)
    tension_scalar = m*g*np.cos(theta) + m * (v.norm()**2)/L
    tension = tension_scalar * (-1*rob_vec.normalise())
    f = tension + m*G
    a = f * (1/m)
    v += a * dt
    end_rod += v*dt

    obj.move_to(end_rod.to_array())

def update_line(obj):
    obj.put_start_and_end_on(start_rod.to_array(), end_rod.to_array())

def update_f_arrow(obj):
    obj.put_start_and_end_on(end_rod.to_array(), (end_rod+f*0.1).to_array())

def update_mg_arrow(obj):
    obj.put_start_and_end_on(end_rod.to_array(), (end_rod+m*G*0.1).to_array())

def update_tension_arrow(obj):
    obj.put_start_and_end_on(end_rod.to_array(), (end_rod+tension*0.1).to_array())

fine_line.add_updater(update_line)
massive_bob.add_updater(update_func)
f_arrow.add_updater(update_f_arrow)
mg_arrow.add_updater(update_mg_arrow)
tension_arrow.add_updater(update_tension_arrow)

scene.wait(50)

scene.hold_on()


from backend import credential
import urllib.parse
from google.cloud import storage
import streamlit as st
import os
import json
import fnmatch
import file_io
import utils
import traceback
import io

def init():
    creds_str = credential.google_creds()

    if not os.path.exists('temp'):
        os.makedirs('temp')

    with open('temp/google-credentials.json', 'w') as f:
        json.dump(creds_str, f)

    os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'temp/google-credentials.json'
    storage_client = storage.Client()
    st.session_state['storage_client'] = storage_client

def upload_to_bucket(root_dir, file, uid, name, metadata=None, compress=None):
    dir = f"{root_dir}/{uid}"
    try:
        # get file extension
        extension = os.path.splitext(file.name)[1]
        filename = name + extension

        compressed_file_path = None

        if compress:
            # Compress file
            if compress == 'gzip':
                compressed_file_path = file_io.compress_to_gzip(file)
                filename += '.gz'  # Add '.gz' extension to the filename
            elif compress == 'xz':
                compressed_file_path = file_io.compress_to_xz(file)
                filename += '.xz'  # Add '.xz' extension to the filename
            else:
                raise ValueError(f'Unsupported compression type: {compress}. Supported types are "gzip" and "xz".'
                                 f'if you do not want to compress the file, set compress=None')

        storage_client = st.session_state['storage_client']
        bucket = storage_client.get_bucket(st.secrets['gcp']['bucket_name'])
        blob = bucket.blob(f"{dir}/{filename}")

        if compress:
            # Open the compressed file in read-binary mode for upload
            with open(compressed_file_path, 'rb') as file_obj:
                file_content = file_obj.read()  # read file content once

                default_meta = {
                    'md5_hash': utils.calculate_md5(file_content),
                    'size': utils.calculate_size(file_content),
                    'owner': st.session_state['student_number'],
                    'time': utils.get_current_time()
                }
                # Merge the default metadata with the given metadata
                meta = {**default_meta, **metadata} if metadata else default_meta

                # Set the blob metadata
                blob.metadata = meta

                blob.upload_from_file(io.BytesIO(file_content))
            # Delete the compressed file
            os.remove(compressed_file_path)
        else:
            # If compress is None or False, upload the file as is
            # Convert file_content to a BytesIO object and upload
            file_content = file.read()
            default_meta = {
                'md5_hash': utils.calculate_md5(file_content),
                'size': utils.calculate_size(file_content),
                'owner': st.session_state['student_number'],
                'time': utils.get_current_time()
            }

            # Merge the default metadata with the given metadata
            meta = {**default_meta, **metadata} if metadata else default_meta

            # Set the blob metadata
            blob.metadata = meta

            blob.upload_from_file(io.BytesIO(file_content))

    except Exception as e:
        tb = traceback.format_exc()
        st.error(f'❌Failed to upload to the bucket: **{e}** \n\n **Traceback**:\n ```{tb}```')
        st.stop()

def delete_from_bucket(root_dir, filenames, uid):
    for filename in filenames:
        # Decode the filename to ensure spaces are handled correctly
        decoded_filename = urllib.parse.unquote(filename)
        try:
            storage_client = st.session_state['storage_client']
            bucket = storage_client.get_bucket(st.secrets['gcp']['bucket_name'])
            blob = bucket.blob(f"{root_dir}/{uid}/{decoded_filename}")
            blob.delete()
        except Exception as e:
            st.error(f'failed to delete file ({root_dir}/{uid}/{decoded_filename}) from bucket. **{e}**')
            st.stop()

def download_from_bucket(root_dir, filename, uid):
    try:
        storage_client = st.session_state['storage_client']
        bucket = storage_client.get_bucket(st.secrets['gcp']['bucket_name'])
        blob = bucket.blob(f"{root_dir}/{uid}/{filename}")

        if not os.path.exists('temp'):
            os.makedirs('temp')

        with open(f"temp/{filename}", 'wb') as f:
            storage_client.download_blob_to_file(blob, f)
        return f"temp/{filename}"
    except Exception as e:
        st.error(f'failed to download file from bucket. **{e}**')
        st.stop()

def get_blobs(bucket, dir, name_pattern, extensions):
    blobs = []
    if '*' in name_pattern:
        # If wildcard is present in name_pattern, process as pattern.
        prefix, pattern = name_pattern.split('*', 1)
        # List blobs whose names start with the given prefix
        for blob in bucket.list_blobs(prefix=f"{dir}/{prefix}"):
            for extension in extensions:
                if blob.name.endswith(extension) and fnmatch.fnmatch(blob.name, f"{dir}/{name_pattern}"):
                    blobs.append(blob)
                    # Once a match is found, no need to check other extensions
                    break
    else:
        # If no wildcard is present, process name_pattern as exact file name.
        for extension in extensions:
            blob = bucket.blob(f"{dir}/{name_pattern}{extension}")
            if blob.exists():
                blobs.append(blob)
    return blobs

def get_public_urls_from_blobs(blobs):
    return [blob.public_url for blob in blobs]

def get_blob_md5(blobs):
    return [blob.md5_hash for blob in blobs]

def get_blob_metadata(blobs):
    return [blob.metadata for blob in blobs]

def get_blob_info(root_dir, uid, name_pattern, extensions, infos):
    storage_client = st.session_state['storage_client']
    bucket = storage_client.get_bucket(st.secrets['gcp']['bucket_name'])
    dir = f"{root_dir}/{uid}"

    blobs = get_blobs(bucket, dir, name_pattern, extensions)

    for info in infos:
        if info == 'url':
            return get_public_urls_from_blobs(blobs)
        else:
            metas = get_blob_metadata(blobs)
            return [meta[info] for meta in metas]


from discord.ext import commands, tasks
import discord
import asyncio
import os
import json
import sqlite3
from dotenv import load_dotenv
import requests
from datetime import datetime,time

load_dotenv()

class Birthday(commands.Cog):
    """Birthday commands."""

    def __init__(self, client):
        self.client = client
        self.birthday_announcments.start()

    @commands.command(hidden = True)
    @commands.is_owner()
    async def force_add_user(self, ctx, user: discord.Member, day: int, month: int):
        """Adds a user to the birthday list."""
        if day > 31 or day < 1 or month > 12 or month < 1:
            await ctx.send("Invalid date.")
            return
        con = sqlite3.connect("databases/user_brithdays.db")
        cur = con.cursor()
        cur.execute("SELECT * FROM birthday WHERE user_id = ?", (user.id,))
        if cur.fetchone() is not None:
            await ctx.send("User already exists.")
            return
        cur.execute("INSERT INTO birthday VALUES (?, ?, ?)", (user.id, day, month))
        con.commit()
        con.close()
        await ctx.send("Added user to birthday list.")

    @commands.command(hidden=True)
    @commands.is_owner()
    async def makeservertablebirthday(self,ctx):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        cur.execute("CREATE TABLE server(ServerID int, Servertoggle, birthdaychannel int,birthdaymessage text)")
        con.commit()
        con.close()
        con = sqlite3.connect("databases/user_brithdays.db")
        cur = con.cursor()
        cur.execute("CREATE TABLE birthday(UsersID int, birthday)")
        con.commit()
        con.close()
        await ctx.send("Done")
#
    #@commands.command(hidden = True)
    #@commands.is_owner()
    #async def setallbithday(self,ctx):
    #    for i in self.client.guilds:
    #        con = sqlite3.connect("databases/server_brithdays.db")
    #        cur = con.cursor()
    #        cur.execute("INSERT INTO server(ServerID, Servertoggle,birthdaychannel) VALUES(?, ?,?)", (i.id, False,None))
    #        await ctx.send(f"{i} has been set")
    #        con.commit()
    #        con.close()

    @commands.Cog.listener()
    async def on_guild_join(self, guild):

        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        cur.execute("INSERT INTO server(ServerID, Servertoggle) VALUES(?, ?)", (guild.id, False))
        con.commit()
        con.close()

    @commands.command(help = " enable and disable Birthday")
    @commands.has_permissions(administrator=True)
    async def toggle_birthday(self,ctx):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        datas = cur.execute("SELECT * FROM server WHERE ServerID=?", (ctx.guild.id,))
        datas = cur.fetchall()
        toggle = datas[0][1]
        if toggle == True:
            cur.execute("UPDATE server SET Servertoggle = ? WHERE ServerID=?", (False, ctx.guild.id,))
            con.commit()
            con.close()
            await ctx.send("Birthday reminders has been turned off")
        if toggle == False:
            cur.execute("UPDATE server SET Servertoggle = ? WHERE ServerID=?", (True, ctx.guild.id,))
            con.commit()
            con.close()
            await ctx.send("Birthday reminders has been turrned on")

    @commands.slash_command(name="toggle_birthday", description="enable and disable Birthday")
    @commands.has_permissions(administrator=True)
    async def _toggle_birthday(self,ctx):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        datas = cur.execute("SELECT * FROM server WHERE ServerID=?", (ctx.guild.id,))
        datas = cur.fetchall()
        toggle = datas[0][1]
        if toggle == True:
            cur.execute("UPDATE server SET Servertoggle = ? WHERE ServerID=?", (False, ctx.guild.id,))
            con.commit()
            con.close()
            await ctx.respond("Birthday reminders has been turned off")
        if toggle == False:
            cur.execute("UPDATE server SET Servertoggle = ? WHERE ServerID=?", (True, ctx.guild.id,))
            con.commit()
            con.close()
            await ctx.respond("Birthday reminders has been turrned on")
        await ctx.followup.send("If you like the bot, please consider voting for it at https://top.gg/bot/902240397273743361 \n It helps a lot! :D", ephemeral=True)

    @commands.slash_command(name="setbirthday", description="Set your birthday use day then month")
    async def setbirthday__slash(self, ctx, day: int, month: int):
        tocken = os.getenv("TOPGG_TOKEN")
        api = requests.get(f"https://top.gg/api/bots/902240397273743361/check?userId={ctx.author.id}", headers={"Authorization": tocken, "Content-Type": "application/json"})
        data = api.json()
        print(api)
        print(data)
        voted = data["voted"]
        #if the api does not return a 200 status code
        if api.status_code != 200:
            voted = 1
            print("api error")
        if voted == 0:
            await ctx.respond("You need to have voted for simplex in the last 24 hours to set your birthday. Please vote and then try again, you can vote here: https://top.gg/bot/902240397273743361/vote",ephemeral=True)
            return
        else:
            if day > 31 or day < 1 or month > 12 or month < 1:
                await ctx.respond("Invalid date.")
            else:
                #force 2 digit date
                if day < 10:
                    day = f"0{day}"
                if month < 10:
                    month = f"0{month}"

                con = sqlite3.connect("databases/user_brithdays.db")
                cur = con.cursor()
                data = cur.execute("SELECT * FROM birthday WHERE UsersID=?", (ctx.author.id,))
                data = cur.fetchall()
                if data == []:
                    cur.execute("INSERT INTO birthday(UsersID, birthday) VALUES(?, ?)", (ctx.author.id, f"{day}/{month}"))
                    con.commit()
                    con.close()
                    await ctx.respond("Your birthday has been set")
                else:
                    cur.execute("UPDATE birthday SET birthday = ? WHERE UsersID=?", (f"{day}/{month}", ctx.author.id,))
                    con.commit()
                    con.close()
                    await ctx.respond("Your birthday has been updated")

    @commands.command(name="setbirthday", help = "Set your birthday use day then month")
    async def setbirthday_commands(self, ctx, day: int, month: int):
        if day > 31 or day < 1 or month > 12 or month < 1:
            await ctx.send("Invalid date.")
        else:
            #formate date 2 digit
            if len(str(day)) == 1:
                day = f"0{day}"
            if len(str(month)) == 1:
                month = f"0{month}"

            con = sqlite3.connect("databases/user_brithdays.db")
            cur = con.cursor()
            data = cur.execute("SELECT * FROM birthday WHERE UsersID=?", (ctx.author.id,))
            data = cur.fetchall()
            if data == []:
                cur.execute("INSERT INTO birthday(UsersID, birthday) VALUES(?, ?)", (ctx.author.id, f"{day}/{month}"))
                con.commit()
                con.close()
                await ctx.send("Your birthday has been set")
            else:
                cur.execute("UPDATE birthday SET birthday = ? WHERE UsersID=?", (f"{day}/{month}", ctx.author.id,))
                con.commit()
                con.close()
                await ctx.send("Your birthday has been updated")

    @commands.command(name="set_birthday_channel",help = "Set the birthday channel")
    @commands.has_permissions(administrator=True)
    async def set_birthday_channel_command(self,ctx, channel: commands.TextChannelConverter):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        cur.execute("UPDATE server SET birthdaychannel = ? WHERE ServerID=?", (channel.id, ctx.guild.id,))
        con.commit()
        con.close()
        await ctx.send(f"Birthday channel has been set to {channel} \n To enable birthday reminders use the command `/toggle_birthday` \n To set a custom message use the command `/birthday_message`")

    @commands.slash_command(name="set_birthday_channel",help = "Set the birthday channel")
    @commands.has_permissions(administrator=True)
    async def set_birthday_channel__slash(self,ctx, channel: commands.TextChannelConverter):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        cur.execute("UPDATE server SET birthdaychannel = ? WHERE ServerID=?", (channel.id, ctx.guild.id,))
        con.commit()
        con.close()
        await ctx.respond(f"Birthday channel has been set to {channel}")

    @commands.slash_command(name="findbirthday", description="Find a users birthday")
    async def findbirthday__slash(self, ctx, user: discord.Member):
        con = sqlite3.connect("databases/user_brithdays.db")
        cur = con.cursor()
        data = cur.execute("SELECT * FROM birthday WHERE UsersID=?", (user.id,))
        data = cur.fetchall()
        if data == []:
            await ctx.respond(f"{user} has not set their birthday")
        else:
            await ctx.respond(f"{user} birthday is {data[0][1]}")
        await ctx.followup.send("If you like the bot, please consider voting for it at https://top.gg/bot/902240397273743361 \n It helps a lot! :D", ephemeral=True)

    @tasks.loop(time=time(7,00))
    async def birthday_announcments(self):
        print("Birthday announcments")
        for server in self.client.guilds:
                print(server)
                con = sqlite3.connect("databases/server_brithdays.db")
                cur = con.cursor()
                datas = cur.execute("SELECT * FROM server WHERE ServerID=?", (server.id,))
                datas = cur.fetchall()
                if datas == []:
                    cur.execute("INSERT INTO server(ServerID, Servertoggle, birthdaychannel) VALUES(?, ?, ?)", (server.id, False, None))
                    con.commit()
                    con.close()
                else:
                    pass
                con = sqlite3.connect("databases/user_brithdays.db")
                cur = con.cursor()
                data = cur.execute("SELECT * FROM birthday")
                data = cur.fetchall()
                if data == []:
                    print("No birthday")
                    #does not work below here
                else:
                    for x in data:
                        if datas[0][1] == True:
                            if datas[0][2] == None:
                                pass
                            else:
                                user = await self.client.fetch_user(x[0])
                                if user in server.members:
                                    channel = await self.client.fetch_channel(datas[0][2])
                                    message = datas[0][3]
                                    if message == None:
                                        message = ":tada:"

                                    print(channel)
                                    print(x[1])
                                    print(datetime.now().strftime("%d/%m"))
                                    if x[1] == datetime.now().strftime("%d/%m"):
                                        print("Birthday")
                                        print(x[0])
                                        await channel.send(f"Happy birthday <@{x[0]}>! \n {message}")
                                else:
                                    username = await self.client.fetch_user(x[0])
                                    print(f"User {username} not in server {x[0]} {server}")
                        else:
                            pass

    #@commands.command()
    #@commands.is_owner()
    #async def foramt_all_birthdays(self,ctx):
    #    con = sqlite3.connect("databases/user_brithdays.db")
    #    cur = con.cursor()
    #    data = cur.execute("SELECT * FROM birthday")
    #    data = cur.fetchall()
    #    for i in data:
    #        day = i[1].split("/")[0]
    #        month = i[1].split("/")[1]
    #        if len(day) == 1:
    #            day = "0" + day
    #        if len(month) == 1:
    #            month = "0" + month
    #        cur.execute("UPDATE birthday SET Birthday = ? WHERE UsersID=?", (f"{day}/{month}", i[0],))
    #        con.commit()
    #    con.close()
    #

    @commands.command()
    @commands.is_owner()
    async def add_message_to_birthday(self,ctx,*,message):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        #creat a new column
        cur.execute("ALTER TABLE server ADD COLUMN birthdaymessage TEXT")
        #set the message
        cur.execute("UPDATE server SET birthdaymessage = ?", (message,))
        con.commit()
        con.close()
        await ctx.send("Done")

    @commands.slash_command(name="birthday_message", description="Add a message to the birthday announcment")
    @commands.has_permissions(administrator=True)
    async def add_message_to_birthday__slash(self,ctx,*,message):
        con = sqlite3.connect("databases/server_brithdays.db")
        cur = con.cursor()
        data = cur.execute("SELECT * FROM server WHERE ServerID=?", (ctx.guild.id,))
        data = cur.fetchall()
        if data == []:
            await ctx.respond("You have not set a birthday channel")
        else:
            cur.execute("UPDATE server SET birthdaymessage = ? WHERE ServerID=?", (message, ctx.guild.id,))
            con.commit()
            con.close()
            await ctx.respond("Done")
            await ctx.followup.send("If you like the bot, please consider voting for it at https://top.gg/bot/902240397273743361 \n It helps a lot! :D", ephemeral=True)

def setup(bot):
    bot.add_cog(Birthday(bot))


from discord.ext import commands

class ErrorHandeler(commands.Cog):
  """A cog for global error handling"""

  def __init__(self, bot: commands.Bot):
   self.bot = bot

  @commands.Cog.listener()
  async def on_command_error(self, ctx: commands.Context, error: commands.CommandError):
    if isinstance(error, commands.MemberNotFound):
      await ctx.send("Please input a valid user")
    if isinstance(error, commands.UnexpectedQuoteError):
      await ctx.send("Your message must be surrounded by quotes.")

def setup(bot: commands.Bot):
    bot.add_cog(ErrorHandeler(bot))


import socket
import time
#traceroute.py 172.217.23.78 udp -p 53 -n 3 -d
class Tracerouter:
    def __init__(self, ip,port,timeout,request,sendwait,debug,data,size):
        self.ip = ip
        self.request = request
        self.timeout = timeout
        self.port = port
        self.sendwait = sendwait
        self.debug = debug
        self.data=data
        self.size=size

    def log(self,message):
        if self.debug:
            with open('logs.txt', "a") as file:
                file.write(message + "\n")

    def send_and_recive(self, send_socket, recv_socket):
        self.log(f'Отправка на {self.ip}')
        send_socket.sendto(b"0" * self.size ,(self.ip, self.port))
        adr = ''
        try:
            _, adr = recv_socket.recvfrom(512)
            adr = adr[0]
            self.log(f'Получение данных от {adr}')
        except socket.timeout as e:
            self.log(f'Вышло время ожидания ответа')
            pass
        return adr

    def ping(self, ttl, send_socket, recv_socket):
        recv_socket.settimeout(self.timeout)
        send_socket.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
        current = None
        start = time.time()
        adr = self.send_and_recive(send_socket,recv_socket)
        if adr != '':
            current = adr
        times = round((time.time() - start) * 1000)
        self.log(f'Время ответа {times} ms')
        return current,times

    def run(self):
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.getprotobyname("udp")) as send_socket:
            with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp")) as recv_socket:
                for ttl in range(1, self.request + 1):
                    self.log(f'Отправка пакета на {self.ip} с ttl {ttl}')
                    current,times= self.ping(ttl, send_socket, recv_socket)
                    if current is None:
                        continue
                    else:
                        self.data.append([str(ttl), current, f'{times} ms'])
                    if current == self.ip:
                        break
                    time.sleep(self.sendwait)
        return self.data


#! /usr/bin/env python

import argparse

parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description='Linearly normalize intensity to between 0 and 255')

parser.add_argument("input_spec", type=str, help="Input specification")
parser.add_argument("out_version", type=str, help="Output image version")
args = parser.parse_args()

import sys
import os

sys.path.append(os.environ['REPO_DIR'] + '/utilities')
from utilities2015 import *
from data_manager import *
from metadata import *
from distributed_utilities import *
from learning_utilities import *

input_spec = load_ini(args.input_spec)
image_name_list = input_spec['image_name_list']
stack = input_spec['stack']
prep_id = input_spec['prep_id']
if prep_id == 'None':
    prep_id = None
resol = input_spec['resol']
version = input_spec['version']
if version == 'None':
    version = None

from scipy.ndimage.interpolation import map_coordinates
from skimage.exposure import rescale_intensity, adjust_gamma
from skimage.transform import rotate

# for section in set(metadata_cache['valid_sections_all'][stack]) - set(metadata_cache['valid_sections'][stack]):
# for section in metadata_cache['valid_sections'][stack]:

for image_name in image_name_list:

#     print "Section", section

    t = time.time()

    img = DataManager.load_image_v2(stack=stack, prep_id=prep_id, fn=image_name, version=version, resol=resol)

    sys.stderr.write('Load image: %.2f seconds.\n' % (time.time() - t))

    t = time.time()
    tb_mask = DataManager.load_thumbnail_mask_v3(stack=stack, prep_id=None, fn=image_name)
#     raw_mask = rescale_by_resampling(tb_mask, new_shape=(img.shape[1], img.shape[0]))
    raw_mask = resize(tb_mask, img.shape) > .5

    save_data(raw_mask,
          DataManager.get_image_filepath_v2(stack=stack, prep_id=prep_id, fn=image_name, version='mask', resol=resol, ext='bp'),
          upload_s3=False)

    sys.stderr.write('Rescale mask: %.2f seconds.\n' % (time.time() - t))

    t = time.time()

    mean_std_all_regions = []
    cx_cy_all_regions = []
    region_size = 5000
    region_spacing = 3000
#     for cx in range(region_size/2, img.shape[1]-region_size/2+1, region_spacing):
#         for cy in range(region_size/2, img.shape[0]-region_size/2+1, region_spacing):
    for cx in range(0, img.shape[1], region_spacing):
        for cy in range(0, img.shape[0], region_spacing):
            region = img[max(cy-region_size/2, 0):min(cy+region_size/2+1, img.shape[0]-1),
                         max(cx-region_size/2, 0):min(cx+region_size/2+1, img.shape[1]-1)]
            region_mask = raw_mask[max(cy-region_size/2, 0):min(cy+region_size/2+1, img.shape[0]-1),
                                   max(cx-region_size/2, 0):min(cx+region_size/2+1, img.shape[1]-1)]
            if np.count_nonzero(region_mask) == 0:
                continue
            mean_std_all_regions.append((region[region_mask].mean(), region[region_mask].std()))
            cx_cy_all_regions.append((cx, cy))

    sys.stderr.write('Compute mean/std for sample regions: %.2f seconds.\n' % (time.time() - t))

    t = time.time()
    mean_map = resample_scoremap(sparse_scores=np.array(mean_std_all_regions)[:,0],
                             sample_locations=cx_cy_all_regions,
                             gridspec=(region_size, region_spacing, img.shape[1], img.shape[0], (0,0)),
                            downscale=4,
                                 interpolation_order=2)

    sys.stderr.write('Interpolate mean map: %.2f seconds.\n' % (time.time() - t)) #10s

    t = time.time()
    mean_map = rescale_by_resampling(mean_map, new_shape=(img.shape[1], img.shape[0]))
    sys.stderr.write('Scale up mean map: %.2f seconds.\n' % (time.time() - t)) #30s

    t = time.time()
    std_map = resample_scoremap(sparse_scores=np.array(mean_std_all_regions)[:,1],
                             sample_locations=cx_cy_all_regions,
                             gridspec=(region_size, region_spacing, img.shape[1], img.shape[0], (0,0)),
                            downscale=4,
                               interpolation_order=2)
    sys.stderr.write('Interpolate std map: %.2f seconds.\n' % (time.time() - t)) #10s

    t = time.time()
    std_map = rescale_by_resampling(std_map, new_shape=(img.shape[1], img.shape[0]))
    sys.stderr.write('Scale up std map: %.2f seconds.\n' % (time.time() - t)) #30s

    # Save mean/std results.

    fp = DataManager.get_intensity_normalization_result_filepath(what='region_centers', stack=stack, fn=image_name)
    create_parent_dir_if_not_exists(fp)
    np.savetxt(fp, cx_cy_all_regions)

    fp = DataManager.get_intensity_normalization_result_filepath(what='mean_std_all_regions', stack=stack, fn=image_name)
    create_parent_dir_if_not_exists(fp)
    np.savetxt(fp, mean_std_all_regions)

    fp = DataManager.get_intensity_normalization_result_filepath(what='mean_map', stack=stack, fn=image_name)
    create_parent_dir_if_not_exists(fp)
    bp.pack_ndarray_file(mean_map.astype(np.float16), fp)

    fp = DataManager.get_intensity_normalization_result_filepath(what='std_map', stack=stack, fn=image_name)
    create_parent_dir_if_not_exists(fp)
    bp.pack_ndarray_file(std_map.astype(np.float16), fp)

    # Export normalized image.

    t = time.time()
    raw_mask = raw_mask & (std_map > 0)
    img_normalized = np.zeros(img.shape, np.float32)
    img_normalized[raw_mask] = (img[raw_mask] - mean_map[raw_mask]) / std_map[raw_mask]
    sys.stderr.write('Normalize: %.2f seconds.\n' % (time.time() - t)) #30s

    t = time.time()
    # FIX THIS! THIS only save uint16, not float16. Need to save as bp instead.
#     img_fp = DataManager.get_image_filepath_v2(stack=stack, prep_id=None, version='NtbNormalizedFloat', resol='down8', section=section, )
#     create_parent_dir_if_not_exists(img_fp)
#     imsave(img_fp, img_normalized[::8, ::8].astype(np.float16))
    save_data(img_normalized.astype(np.float16),
              DataManager.get_intensity_normalization_result_filepath(what='normalized_float_map', stack=stack, fn=image_name),
             upload_s3=False)
    sys.stderr.write('Save float version: %.2f seconds.\n' % (time.time() - t)) #30s

#     t = time.time()
#     img_normalized_uint8 = rescale_intensity_v2(img_normalized, -1, 6)
#     sys.stderr.write('Rescale to uint8: %.2f seconds.\n' % (time.time() - t)) #30s

#     t = time.time()
#     img_fp = DataManager.get_image_filepath_v2(stack=stack, prep_id=None, version='NtbNormalized', resol='raw', section=section)
#     create_parent_dir_if_not_exists(img_fp)
#     imsave(img_fp, img_normalized_uint8)
#     sys.stderr.write('Save uint8 version: %.2f seconds.\n' % (time.time() - t)) #30s

    # Export histogram.

    plt.hist(img_normalized[raw_mask].flatten(), bins=100, log=True);
    fp = DataManager.get_intensity_normalization_result_filepath(what='float_histogram_png', stack=stack, fn=image_name)
    create_parent_dir_if_not_exists(fp)
    plt.savefig(fp)
    plt.close();

#     hist_fp = DataManager.get_intensity_normalization_result_filepath(what='float_histogram', stack=stack, section=section)
#     create_parent_dir_if_not_exists(hist_fp)

#     hist, bin_edges = np.histogram(img_normalized[valid_mask].flatten(), bins=np.arange(0,201,5));

#     plt.bar(bin_edges[:-1], np.log(hist));
#     plt.xticks(np.arange(0, 200, 20), np.arange(0, 200, 20));
#     plt.xlabel('Normalized pixel value (float)');
#     plt.title(metadata_cache['sections_to_filenames'][stack][section])

#     plt.savefig(hist_fp)
#     plt.close();

gamma_map = img_as_ubyte(adjust_gamma(np.arange(0, 256, 1) / 255., 8.))

low = -2.
high = 50.

for image_name in image_name_list:

    img_normalized = load_data(
              DataManager.get_intensity_normalization_result_filepath(what='normalized_float_map', stack=stack, fn=image_name),
             download_s3=False)

    t = time.time()
    img_normalized_uint8 = rescale_intensity_v2(img_normalized, low, high)
    sys.stderr.write('Rescale to uint8: %.2f seconds.\n' % (time.time() - t))

    t = time.time()
    raw_mask = load_data(DataManager.get_image_filepath_v2(stack=stack, prep_id=prep_id, fn=image_name, version='mask', resol=resol, ext='bp'),
                        download_s3=False)
    img_normalized_uint8[~raw_mask] = 0
    sys.stderr.write('Load mask: %.2f seconds.\n' % (time.time() - t))

    img = 255 - img_normalized_uint8
    save_data(gamma_map[img],
              DataManager.get_image_filepath_v2(stack=stack, prep_id=prep_id, fn=image_name, version=args.out_version, resol=resol),
             upload_s3=False)


# -*- coding: utf-8 -*-
import sys
import re
import pdb
def main(args):
    #pdb.set_trace()
    lines = args[1].decode("gb18030").encode("utf8").split("|||")
    for line in lines:
        if re.search(r"^(\S+)",line):
            s = re.search(r"^(\S+)",line)
            ss = s.group(1)
            print(ss.decode("utf8").encode("gb18030"))
            break

if __name__=="__main__":
    main(sys.argv)