#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""humidistat_gui.py

Manages the graphical user interface
"""
__author__ = "Dennis van Gils"
__authoremail__ = "vangils.dennis@gmail.com"
__url__ = "https://github.com/Dennis-van-Gils/project-Humidistat"
__date__ = "28-07-2022"
__version__ = "1.1"
# pylint: disable=bare-except, broad-except, unnecessary-lambda

from pathlib import Path
from configparser import ConfigParser

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QDateTime
from PyQt5.QtWidgets import (
    QCheckBox,
    QFileDialog,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QRadioButton,
    QSpacerItem,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)
import pyqtgraph as pg
import dvg_monkeypatch_pyqtgraph  # pylint: disable=unused-import

import dvg_pyqt_controls as controls
from dvg_debug_functions import dprint, tprint, print_fancy_traceback as pft
from dvg_pyqt_filelogger import FileLogger
from dvg_pyqtgraph_threadsafe import (
    HistoryChartCurve,
    LegendSelect,
    PlotManager,
)

from dvg_devices.Arduino_protocol_serial import Arduino
from humidistat_qdev import Humidistat_qdev, ControlMode, ControlBand

# Constants
UPDATE_INTERVAL_WALL_CLOCK = 50  # 50 [ms]
CHART_HISTORY_TIME = 7200  # Maximum history length of charts [s]
DEFAULT_CONFIG_FILE = "./config/humidistat_default_config.ini"

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

# Try OpenGL support
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)

# Default settings for graphs
# pg.setConfigOptions(leftButtonPan=False)
pg.setConfigOption("background", controls.COLOR_GRAPH_BG)
pg.setConfigOption("foreground", controls.COLOR_GRAPH_FG)

# ------------------------------------------------------------------------------
#   Custom plotting styles
# ------------------------------------------------------------------------------

class CustomAxis(pg.AxisItem):
    """Aligns the top label of a `pyqtgraph.PlotItem` plot to the top-left
    corner
    """

    def resizeEvent(self, ev=None):
        if self.orientation == "top":
            self.label.setPos(QtCore.QPointF(0, 0))

def apply_PlotItem_style(
    pi: pg.PlotItem,
    title: str = "",
    bottom: str = "",
    left: str = "",
    right: str = "",
):
    """Apply our custom stylesheet to a `pyqtgraph.PlotItem` plot"""

    pi.setClipToView(True)
    pi.showGrid(x=1, y=1)
    pi.setMenuEnabled(True)
    pi.enableAutoRange(axis=pg.ViewBox.XAxis, enable=False)
    pi.enableAutoRange(axis=pg.ViewBox.YAxis, enable=True)
    pi.setAutoVisible(y=True)
    pi.setRange(xRange=[-CHART_HISTORY_TIME, 0])
    pi.vb.setLimits(xMax=0.01)

    p_title = {
        "color": controls.COLOR_GRAPH_FG.name(),
        "font-size": "12pt",
        "font-family": "Helvetica",
        "font-weight": "bold",
    }
    p_label = {
        "color": controls.COLOR_GRAPH_FG.name(),
        "font-size": "12pt",
        "font-family": "Helvetica",
    }
    pi.setLabel("bottom", bottom, **p_label)
    pi.setLabel("left", left, **p_label)
    pi.setLabel("top", title, **p_title)
    pi.setLabel("right", right, **p_label)

    # fmt: off
    font = QtGui.QFont()
    font.setPixelSize(16)
    pi.getAxis("bottom").setTickFont(font)
    pi.getAxis("left")  .setTickFont(font)
    pi.getAxis("top")   .setTickFont(font)
    pi.getAxis("right") .setTickFont(font)

    pi.getAxis("bottom").setStyle(tickTextOffset=10)
    pi.getAxis("left")  .setStyle(tickTextOffset=10)

    pi.getAxis("bottom").setHeight(60)
    pi.getAxis("left")  .setWidth(90)
    pi.getAxis("top")   .setHeight(40)
    pi.getAxis("right") .setWidth(16)

    pi.getAxis("top")  .setStyle(showValues=False)
    pi.getAxis("right").setStyle(showValues=False)
    # fmt: on

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

class MainWindow(QWidget):
    def __init__(
        self,
        ard: Arduino,
        ard_qdev: Humidistat_qdev,
        logger: FileLogger,
        parent=None,
        **kwargs,
    ):
        super().__init__(parent, **kwargs)

        self.ard = ard
        self.ard_qdev = ard_qdev
        self.logger = logger

        # Shorthands
        state = self.ard_qdev.state

        self.setWindowTitle("Humidistat")
        self.setGeometry(350, 60, 1200, 900)
        self.setStyleSheet(
            controls.SS_TEXTBOX_READ_ONLY
            + controls.SS_GROUP
            + controls.SS_HOVER
        )

        # Textbox widths for fitting N characters using the current font
        ex8 = 8 + 8 * QtGui.QFontMetrics(QtGui.QFont()).averageCharWidth()
        ex10 = 8 + 10 * QtGui.QFontMetrics(QtGui.QFont()).averageCharWidth()

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

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

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

        # Middle box
        self.qlbl_title = QLabel(
            "Humidistat",
            font=QtGui.QFont("Palatino", 14, weight=QtGui.QFont.Bold),
        )
        self.qlbl_title.setAlignment(QtCore.Qt.AlignCenter)
        self.qlbl_cur_date_time = QLabel("00-00-0000    00:00:00")
        self.qlbl_cur_date_time.setAlignment(QtCore.Qt.AlignCenter)
        self.qpbt_record = controls.create_Toggle_button(
            "Click to start recording to file"
        )
        self.qpbt_record.clicked.connect(
            lambda state: self.logger.record(state)
        )

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

        # Right box
        p = {"alignment": QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter}
        self.qpbt_exit = QPushButton("Exit", minimumHeight=30)
        self.qpbt_exit.clicked.connect(self.close)
        self.qlbl_GitHub = QLabel(
            'Documentation' % __url__, **p
        )
        self.qlbl_GitHub.setTextFormat(QtCore.Qt.RichText)
        self.qlbl_GitHub.setTextInteractionFlags(
            QtCore.Qt.TextBrowserInteraction
        )
        self.qlbl_GitHub.setOpenExternalLinks(True)

        vbox_right = QVBoxLayout(spacing=4)
        vbox_right.addWidget(self.qpbt_exit, stretch=0)
        vbox_right.addStretch(1)
        vbox_right.addWidget(QLabel(__author__, **p))
        vbox_right.addWidget(self.qlbl_GitHub)
        vbox_right.addWidget(QLabel("v%s" % __version__, **p))

        # Round up top frame
        hbox_top = 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()

        # Plots
        self.pi_humi = self.gw.addPlot(
            row=0, col=0, axisItems={"top": CustomAxis(orientation="top")}
        )
        self.pi_temp = self.gw.addPlot(
            row=1, col=0, axisItems={"top": CustomAxis(orientation="top")}
        )
        self.pi_pres = self.gw.addPlot(
            row=2, col=0, axisItems={"top": CustomAxis(orientation="top")}
        )
        apply_PlotItem_style(self.pi_humi, title="Humidity", left="% RH")
        apply_PlotItem_style(self.pi_temp, title="Temperature", left="°C")
        apply_PlotItem_style(self.pi_pres, title="Pressure", left="mbar")
        self.plots = [self.pi_temp, self.pi_humi, self.pi_pres]

        # Thread-safe curves
        capacity = round(
            CHART_HISTORY_TIME * 1e3 / ard_qdev.worker_DAQ._DAQ_interval_ms
        )  # TODO: Fix this wrong calculation. `_DAQ_interval_ms` is not the
        # correct variable anymore. DAQ interval is rather determined on the
        # Arduino side.
        PEN_01 = pg.mkPen(controls.COLOR_PEN_TURQUOISE, width=3)
        PEN_02 = pg.mkPen(controls.COLOR_PEN_YELLOW, width=3)
        PEN_03 = pg.mkPen(controls.COLOR_PEN_PINK, width=3)
        PEN_04 = pg.mkPen(
            controls.COLOR_PEN_PINK, width=1, style=QtCore.Qt.DotLine
        )

        self.curve_setpoint = HistoryChartCurve(  # Setpoint
            capacity=capacity,
            linked_curve=self.pi_humi.plot(pen=PEN_03, name=""),
        )
        self.curve_deadband_HI = HistoryChartCurve(  # Dead-band HI
            capacity=capacity,
            linked_curve=self.pi_humi.plot(pen=PEN_04, name=""),
        )
        self.curve_deadband_LO = HistoryChartCurve(  # Dead-band LO
            capacity=capacity,
            linked_curve=self.pi_humi.plot(pen=PEN_04, name=""),
        )
        self.curve_humi_1 = HistoryChartCurve(  # Sensor 1: Humidity
            capacity=capacity,
            linked_curve=self.pi_humi.plot(pen=PEN_01, name="H"),
        )
        self.curve_temp_1 = HistoryChartCurve(  # Sensor 1: Temperature
            capacity=capacity,
            linked_curve=self.pi_temp.plot(pen=PEN_01, name="T"),
        )
        self.curve_pres_1 = HistoryChartCurve(  # Sensor 1: Pressure
            capacity=capacity,
            linked_curve=self.pi_pres.plot(pen=PEN_01, name="P"),
        )
        self.curve_humi_2 = HistoryChartCurve(  # Sensor 2: Humidity
            capacity=capacity,
            linked_curve=self.pi_humi.plot(pen=PEN_02, name="H"),
        )
        self.curve_temp_2 = HistoryChartCurve(  # Sensor 2: Temperature
            capacity=capacity,
            linked_curve=self.pi_temp.plot(pen=PEN_02, name="T"),
        )
        self.curve_pres_2 = HistoryChartCurve(  # Sensor 2: Pressure
            capacity=capacity,
            linked_curve=self.pi_pres.plot(pen=PEN_02, name="P"),
        )

        self.curves_setpoint = [
            self.curve_setpoint,
            self.curve_deadband_HI,
            self.curve_deadband_LO,
        ]
        self.curves_1 = [
            self.curve_humi_1,
            self.curve_temp_1,
            self.curve_pres_1,
        ]
        self.curves_2 = [
            self.curve_humi_2,
            self.curve_temp_2,
            self.curve_pres_2,
        ]
        self.curves = self.curves_setpoint + self.curves_1 + self.curves_2

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

        legend_1 = LegendSelect(linked_curves=self.curves_1)
        legend_2 = LegendSelect(linked_curves=self.curves_2)
        legend_1.qpbt_toggle.clicked.connect(
            lambda: QtCore.QCoreApplication.processEvents()  # Force redraw
        )
        legend_2.qpbt_toggle.clicked.connect(
            lambda: QtCore.QCoreApplication.processEvents()  # Force redraw
        )

        p = {
            "readOnly": True,
            "alignment": QtCore.Qt.AlignRight,
            "maximumWidth": 54,
        }
        self.qlin_humi_1 = QLineEdit(**p)
        self.qlin_temp_1 = QLineEdit(**p)
        self.qlin_pres_1 = QLineEdit(**p)
        self.qlin_humi_2 = QLineEdit(**p)
        self.qlin_temp_2 = QLineEdit(**p)
        self.qlin_pres_2 = QLineEdit(**p)

        # fmt: off
        legend_1.grid.setHorizontalSpacing(6)
        legend_1.grid.addWidget(self.qlin_humi_1  , 0, 2)
        legend_1.grid.addWidget(QLabel("± 3 % RH"), 0, 3)
        legend_1.grid.addWidget(self.qlin_temp_1  , 1, 2)
        legend_1.grid.addWidget(QLabel("± 0.5 °C"), 1, 3)
        legend_1.grid.addWidget(self.qlin_pres_1  , 2, 2)
        legend_1.grid.addWidget(QLabel("± 1 mbar"), 2, 3)
        legend_1.grid.setColumnStretch(0, 0)
        legend_1.grid.setColumnStretch(1, 0)

        legend_2.grid.setHorizontalSpacing(6)
        legend_2.grid.addWidget(self.qlin_humi_2  , 0, 2)
        legend_2.grid.addWidget(QLabel("± 3 % RH"), 0, 3)
        legend_2.grid.addWidget(self.qlin_temp_2  , 1, 2)
        legend_2.grid.addWidget(QLabel("± 0.5 °C"), 1, 3)
        legend_2.grid.addWidget(self.qlin_pres_2  , 2, 2)
        legend_2.grid.addWidget(QLabel("± 1 mbar"), 2, 3)
        legend_2.grid.setColumnStretch(0, 0)
        legend_2.grid.setColumnStretch(1, 0)
        # fmt: on

        vbox = QVBoxLayout(spacing=4)
        vbox.addWidget(QLabel("Sensor #1"))
        vbox.addLayout(legend_1.grid)
        vbox.addSpacing(6)
        vbox.addWidget(QLabel("Sensor #2"))
        vbox.addLayout(legend_2.grid)

        qgrp_readings = QGroupBox("Readings")
        qgrp_readings.setLayout(vbox)

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

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

        qgrp_comments = 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.curves,
            presets=[
                {
                    "button_label": "01:00",
                    "x_axis_label": "sec",
                    "x_axis_divisor": 1,
                    "x_axis_range": (-60, 0),
                },
                {
                    "button_label": "03:00",
                    "x_axis_label": "sec",
                    "x_axis_divisor": 1,
                    "x_axis_range": (-180, 0),
                },
                {
                    "button_label": "10:00",
                    "x_axis_label": "min",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-10, 0),
                },
                {
                    "button_label": "30:00",
                    "x_axis_label": "min",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-30, 0),
                },
                {
                    "button_label": "60:00",
                    "x_axis_label": "min",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-60, 0),
                },
                {
                    "button_label": "120:00",
                    "x_axis_label": "min",
                    "x_axis_divisor": 60,
                    "x_axis_range": (-120, 0),
                },
            ],
        )
        self.plot_manager.add_clear_button(linked_curves=self.curves)
        self.plot_manager.perform_preset(1)

        qgrp_charts = QGroupBox("Charts")
        qgrp_charts.setLayout(self.plot_manager.grid)

        #  Group 'Control'
        # -------------------------

        p = {"maximumWidth": ex10 / 2, "alignment": QtCore.Qt.AlignRight}
        self.qlin_setpoint = QLineEdit(**p)
        self.qlin_control_band = QLineEdit(
            readOnly=True, maximumWidth=80, alignment=QtCore.Qt.AlignHCenter
        )
        self.qpbt_control_mode = controls.create_Toggle_button("Manual control")
        self.qpbt_valve_1 = controls.create_Toggle_button(maximumWidth=80)
        self.qpbt_valve_2 = controls.create_Toggle_button(maximumWidth=80)
        self.qpbt_pump = controls.create_Toggle_button(maximumWidth=80)
        self.qpbt_burst_incr_RH = QPushButton("RH ▲ burst")
        self.qpbt_burst_decr_RH = QPushButton("RH ▼ burst")
        self.qpbt_reconnect = QPushButton("Reconnect sensors")

        self.qlin_setpoint.editingFinished.connect(self.process_qlin_setpoint)
        self.qpbt_control_mode.clicked.connect(self.process_qpbt_control_mode)
        self.qpbt_valve_1.clicked.connect(
            lambda: ard_qdev.set_valve_1(not state.valve_1)
        )
        self.qpbt_valve_2.clicked.connect(
            lambda: ard_qdev.set_valve_2(not state.valve_2)
        )
        self.qpbt_pump.clicked.connect(
            lambda: ard_qdev.set_pump(not state.pump)
        )
        self.qpbt_burst_incr_RH.clicked.connect(ard_qdev.burst_incr_RH)
        self.qpbt_burst_decr_RH.clicked.connect(ard_qdev.burst_decr_RH)
        self.qpbt_reconnect.clicked.connect(ard_qdev.reconnect_BME280_sensors)

        legend_setpoint = LegendSelect(
            linked_curves=[self.curve_setpoint], hide_toggle_button=True
        )

        # Show/hide dead-band curves when clicking setpoint checkbox
        def curves_deadband_setVisible(flag: bool):
            self.curve_deadband_HI.setVisible(flag)
            self.curve_deadband_LO.setVisible(flag)

        legend_setpoint.chkbs[0].clicked.connect(
            lambda checked: curves_deadband_setVisible(checked)
        )

        # fmt: off
        i = 0
        grid = QGridLayout(spacing=4)
        grid.addWidget(QLabel("Setpoint:")       , i, 0)
        grid.addWidget(self.qlin_setpoint        , i, 1)
        grid.addWidget(QLabel("% RH")            , i, 2)         ; i+=1
        grid.addLayout(legend_setpoint.grid      , i, 1, 1, 2)   ; i+=1
        grid.addWidget(QLabel("Band:")           , i, 0)
        grid.addWidget(self.qlin_control_band    , i, 1, 1, 2)   ; i+=1
        grid.addItem(QSpacerItem(0, 10)          , i, 0)         ; i+=1
        grid.addWidget(QLabel("Actuators"), i, 0, 1, 3)   ; i+=1
        grid.addWidget(self.qpbt_control_mode    , i, 0, 1, 3)   ; i+=1
        grid.addWidget(QLabel("valve 1")         , i, 0)
        grid.addWidget(self.qpbt_valve_1         , i, 1, 1, 2)   ; i+=1
        grid.addWidget(QLabel("valve 2")         , i, 0)
        grid.addWidget(self.qpbt_valve_2         , i, 1, 1, 2)   ; i+=1
        grid.addWidget(QLabel("pump")            , i, 0)
        grid.addWidget(self.qpbt_pump            , i, 1, 1, 2)   ; i+=1
        grid.addItem(QSpacerItem(0, 6)           , i, 0)         ; i+=1
        grid.addWidget(self.qpbt_burst_incr_RH   , i, 0, 1, 3)   ; i+=1
        grid.addWidget(self.qpbt_burst_decr_RH   , i, 0, 1, 3)   ; i+=1
        grid.addItem(QSpacerItem(0, 6)           , i, 0)         ; i+=1
        grid.addWidget(QLabel("Troubleshoot"), i, 0, 1, 3); i+=1
        grid.addWidget(self.qpbt_reconnect       , i, 0, 1, 3)   ; i+=1
        # fmt: on

        qgrp_control = QGroupBox("Control")
        qgrp_control.setLayout(grid)

        #  Group 'Configuration'
        # -------------------------

        # fmt: off
        p = {"maximumWidth": ex10}
        self.qchk_incr_ENA_valve_1 = QCheckBox("valve 1", **p)
        self.qchk_incr_ENA_valve_2 = QCheckBox("valve 2", **p)
        self.qchk_incr_ENA_pump    = QCheckBox("pump"   , **p)
        self.qchk_decr_ENA_valve_1 = QCheckBox("valve 1", **p)
        self.qchk_decr_ENA_valve_2 = QCheckBox("valve 2", **p)
        self.qchk_decr_ENA_pump    = QCheckBox("pump"   , **p)
        self.qrbt_act_on_sensor_1  = QRadioButton("sensor 1")
        self.qrbt_act_on_sensor_2  = QRadioButton("sensor 2")
        # fmt: on

        self.qchk_incr_ENA_valve_1.clicked.connect(
            self.process_qchk_incr_ENA_valve_1
        )
        self.qchk_decr_ENA_valve_1.clicked.connect(
            self.process_qchk_decr_ENA_valve_1
        )
        self.qchk_incr_ENA_valve_2.clicked.connect(
            self.process_qchk_incr_ENA_valve_2
        )
        self.qchk_decr_ENA_valve_2.clicked.connect(
            self.process_qchk_decr_ENA_valve_2
        )
        self.qchk_incr_ENA_pump.clicked.connect(self.process_qchk_incr_ENA_pump)
        self.qchk_decr_ENA_pump.clicked.connect(self.process_qchk_decr_ENA_pump)
        self.qrbt_act_on_sensor_1.clicked.connect(
            self.process_qrbt_act_on_sensor_1
        )
        self.qrbt_act_on_sensor_2.clicked.connect(
            self.process_qrbt_act_on_sensor_2
        )

        p = {"maximumWidth": ex8, "alignment": QtCore.Qt.AlignRight}
        self.qlin_fineband_dHI = QLineEdit(**p)
        self.qlin_fineband_dLO = QLineEdit(**p)
        self.qlin_deadband_dHI = QLineEdit(**p)
        self.qlin_deadband_dLO = QLineEdit(**p)
        self.qlin_burst_update_period = QLineEdit(**p)
        self.qlin_burst_incr_RH_length = QLineEdit(**p)
        self.qlin_burst_decr_RH_length = QLineEdit(**p)

        self.qlin_fineband_dHI.editingFinished.connect(
            self.process_qlin_fineband_dHI
        )
        self.qlin_fineband_dLO.editingFinished.connect(
            self.process_qlin_fineband_dLO
        )
        self.qlin_deadband_dHI.editingFinished.connect(
            self.process_qlin_deadband_dHI
        )
        self.qlin_deadband_dLO.editingFinished.connect(
            self.process_qlin_deadband_dLO
        )
        self.qlin_burst_update_period.editingFinished.connect(
            self.process_qlin_burst_update_period
        )
        self.qlin_burst_incr_RH_length.editingFinished.connect(
            self.process_qlin_burst_incr_RH_length
        )
        self.qlin_burst_decr_RH_length.editingFinished.connect(
            self.process_qlin_burst_decr_RH_length
        )

        self.qpbt_load_config = QPushButton("Load", maximumWidth=ex8)
        self.qpbt_save_config = QPushButton("Save", maximumWidth=ex8)
        self.qpbt_dflt_config = QPushButton(
            "Save as default", maximumWidth=ex8 * 2
        )
        self.qpbt_load_config.clicked.connect(
            lambda: self.load_config_from_file(from_default=False)
        )
        self.qpbt_save_config.clicked.connect(
            lambda: self.save_config_to_file(as_default=False)
        )
        self.qpbt_dflt_config.clicked.connect(
            lambda: self.save_config_to_file(as_default=True)
        )

        grid3 = QGridLayout(spacing=4)
        grid3.addWidget(QLabel("Configuration"), 0, 0, 1, 3)
        grid3.addWidget(self.qpbt_load_config, 1, 0)
        grid3.addWidget(self.qpbt_save_config, 1, 1)
        grid3.addWidget(self.qpbt_dflt_config, 1, 2)

        # fmt: off
        i = 0
        grid2 = QGridLayout(spacing=4)
        grid2.addWidget(QLabel("Control bandwidths"), i, 0, 1, 3); i+=1
        grid2.addWidget(QLabel("Fine-band:")               , i, 0)
        grid2.addWidget(self.qlin_fineband_dLO             , i, 1)
        grid2.addWidget(self.qlin_fineband_dHI             , i, 2)
        grid2.addWidget(QLabel("% RH")                     , i, 3)      ; i+=1
        grid2.addWidget(QLabel("Dead-band:")               , i, 0)
        grid2.addWidget(self.qlin_deadband_dLO             , i, 1)
        grid2.addWidget(self.qlin_deadband_dHI             , i, 2)
        grid2.addWidget(QLabel("% RH")                     , i, 3)      ; i+=1
        grid2.addItem(QSpacerItem(0, 6)                    , i, 0)      ; i+=1
        grid2.addWidget(QLabel("Fine-band bursts")  , i, 0, 1, 3); i+=1
        grid2.addWidget(QLabel("Update period:")           , i, 0, 1, 2)
        grid2.addWidget(self.qlin_burst_update_period      , i, 2)
        grid2.addWidget(QLabel("s")                        , i, 3)      ; i+=1
        grid2.addWidget(QLabel("RH ▲ burst length:")       , i, 0, 1, 2)
        grid2.addWidget(self.qlin_burst_incr_RH_length     , i, 2)
        grid2.addWidget(QLabel("ms")                       , i, 3)      ; i+=1
        grid2.addWidget(QLabel("RH ▼ burst length:")       , i, 0, 1, 2)
        grid2.addWidget(self.qlin_burst_decr_RH_length     , i, 2)
        grid2.addWidget(QLabel("ms")                       , i, 3)      ; i+=1

        i = 0
        grid = QGridLayout(spacing=4)
        grid.addWidget(QLabel("Assign actuators")   , i, 0, 1, 3); i+=1
        grid.addWidget(QLabel("RH ▲:")                     , i, 0)
        grid.addWidget(self.qchk_incr_ENA_valve_1          , i, 1)
        grid.addWidget(QLabel("RH ▼:")                     , i, 2)
        grid.addWidget(self.qchk_decr_ENA_valve_1          , i, 3)      ; i+=1
        grid.addWidget(self.qchk_incr_ENA_valve_2          , i, 1)
        grid.addWidget(self.qchk_decr_ENA_valve_2          , i, 3)      ; i+=1
        grid.addWidget(self.qchk_incr_ENA_pump             , i, 1)
        grid.addWidget(self.qchk_decr_ENA_pump             , i, 3)      ; i+=1
        grid.addItem(QSpacerItem(0, 6)                     , i, 0)      ; i+=1
        grid.addWidget(QLabel("Act on:")                   , i, 0)
        grid.addWidget(self.qrbt_act_on_sensor_1           , i, 1, 1, 2); i+=1
        grid.addWidget(self.qrbt_act_on_sensor_2           , i, 1, 1, 2); i+=1
        grid.addItem(QSpacerItem(0, 4)                     , i, 0)      ; i+=1
        grid.addLayout(grid2                               , i, 0, 1, 4); i+=1
        grid.addItem(QSpacerItem(0, 4)                     , i, 0)      ; i+=1
        grid.addLayout(grid3                               , i, 0, 1, 4)
        # fmt: on

        qgrp_config = QGroupBox("Configuration")
        qgrp_config.setLayout(grid)

        #  Round up bottom frame
        # -------------------------

        hbox1 = QHBoxLayout()
        hbox1.addWidget(qgrp_control, alignment=QtCore.Qt.AlignLeft)
        hbox1.addWidget(qgrp_config, alignment=QtCore.Qt.AlignLeft)

        hbox2 = QHBoxLayout()
        hbox2.addWidget(qgrp_readings, alignment=QtCore.Qt.AlignLeft)
        hbox2.addWidget(qgrp_charts, alignment=QtCore.Qt.AlignLeft)

        vbox = QVBoxLayout()
        vbox.addLayout(hbox1)
        vbox.addLayout(hbox2)
        vbox.addWidget(qgrp_comments)

        grid_bot = QGridLayout()
        grid_bot.addWidget(self.gw, 0, 0)
        grid_bot.addLayout(vbox, 0, 1)
        grid_bot.setColumnStretch(0, 1)
        grid_bot.setColumnStretch(1, 0)

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

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

        self.populate_configuration()

        # -------------------------
        #   Wall clock timer
        # -------------------------

        self.timer_wall_clock = QtCore.QTimer()
        self.timer_wall_clock.timeout.connect(self.update_wall_clock)
        self.timer_wall_clock.start(UPDATE_INTERVAL_WALL_CLOCK)

        # -------------------------
        #   Connect external signals
        # -------------------------

        self.ard_qdev.signal_DAQ_updated.connect(self.update_GUI)

        self.logger.signal_recording_started.connect(
            lambda filepath: self.qpbt_record.setText(
                "Recording to file: %s" % filepath
            )
        )
        self.logger.signal_recording_stopped.connect(
            lambda: self.qpbt_record.setText("Click to start recording to file")
        )

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

    @QtCore.pyqtSlot()
    def update_wall_clock(self):
        cur_date_time = QDateTime.currentDateTime()
        self.qlbl_cur_date_time.setText(
            "%s    %s"
            % (
                cur_date_time.toString("dd-MM-yyyy"),
                cur_date_time.toString("HH:mm:ss"),
            )
        )

    @QtCore.pyqtSlot()
    def update_GUI(self):
        # Shorthands
        ard_qdev = self.ard_qdev
        state = self.ard_qdev.state

        self.qlbl_update_counter.setText("%i" % ard_qdev.update_counter_DAQ)
        self.qlbl_DAQ_rate.setText(
            "DAQ: %.1f Hz" % ard_qdev.obtained_DAQ_rate_Hz
        )
        if self.logger.is_recording():
            self.qlbl_recording_time.setText(
                "REC: %s" % self.logger.pretty_elapsed()
            )
        else:
            self.qlbl_recording_time.setText("")

        self.qlin_humi_1.setText("%.1f" % state.humi_1)
        self.qlin_temp_1.setText("%.1f" % state.temp_1)
        self.qlin_pres_1.setText("%.0f" % state.pres_1)
        self.qlin_humi_2.setText("%.1f" % state.humi_2)
        self.qlin_temp_2.setText("%.1f" % state.temp_2)
        self.qlin_pres_2.setText("%.0f" % state.pres_2)

        if state.control_band == ControlBand.Coarse:
            self.qlin_control_band.setText("COARSE")
        elif state.control_band == ControlBand.Fine:
            self.qlin_control_band.setText("FINE")
        elif state.control_band == ControlBand.Dead:
            self.qlin_control_band.setText("DEAD")

        self.qpbt_valve_1.setChecked(state.valve_1)
        self.qpbt_valve_1.setText("ON" if state.valve_1 else "OFF")
        self.qpbt_valve_2.setChecked(state.valve_2)
        self.qpbt_valve_2.setText("ON" if state.valve_2 else "OFF")
        self.qpbt_pump.setChecked(state.pump)
        self.qpbt_pump.setText("ON" if state.pump else "OFF")

        if DEBUG:
            tprint("update_charts")

        for curve in self.curves:
            curve.update()

    @QtCore.pyqtSlot()
    def populate_configuration(self):
        # Shorthands
        state = self.ard_qdev.state
        config = self.ard_qdev.config

        self.qlin_setpoint.setText("%u" % state.setpoint)

        self.qchk_incr_ENA_valve_1.setChecked(config.actors_incr_RH.ENA_valve_1)
        self.qchk_incr_ENA_valve_2.setChecked(config.actors_incr_RH.ENA_valve_2)
        self.qchk_incr_ENA_pump.setChecked(config.actors_incr_RH.ENA_pump)

        self.qchk_decr_ENA_valve_1.setChecked(config.actors_decr_RH.ENA_valve_1)
        self.qchk_decr_ENA_valve_2.setChecked(config.actors_decr_RH.ENA_valve_2)
        self.qchk_decr_ENA_pump.setChecked(config.actors_decr_RH.ENA_pump)

        self.qrbt_act_on_sensor_1.setChecked(config.act_on_sensor_no == 1)
        self.qrbt_act_on_sensor_2.setChecked(config.act_on_sensor_no == 2)

        self.qlin_fineband_dHI.setText("%+.1f" % config.fineband_dHI)
        self.qlin_fineband_dLO.setText("%+.1f" % config.fineband_dLO)
        self.qlin_deadband_dHI.setText("%+.1f" % config.deadband_dHI)
        self.qlin_deadband_dLO.setText("%+.1f" % config.deadband_dLO)
        self.qlin_burst_update_period.setText("%u" % config.burst_update_period)
        self.qlin_burst_incr_RH_length.setText(
            "%u" % config.burst_incr_RH_length
        )
        self.qlin_burst_decr_RH_length.setText(
            "%u" % config.burst_decr_RH_length
        )

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

    @QtCore.pyqtSlot()
    def process_qlin_setpoint(self):
        try:
            val = int(self.qlin_setpoint.text())
        except ValueError:
            val = self.ard_qdev.state.setpoint

        val = max(val, 0)
        val = min(val, 100)
        self.qlin_setpoint.setText("%u" % val)
        self.ard_qdev.state.setpoint = val

    @QtCore.pyqtSlot()
    def process_qpbt_control_mode(self):
        if self.qpbt_control_mode.isChecked():
            # Switch to auto control
            self.qpbt_control_mode.setText("Auto control")
            self.ard_qdev.state.control_mode = ControlMode.Auto
        else:
            # Switch to manual control
            # Will automatically turn off all actuators
            self.qpbt_control_mode.setText("Manual control")
            self.ard_qdev.state.control_mode = ControlMode.Manual
            self.ard_qdev.set_actuators(False, False, False)

        flag = self.ard_qdev.state.control_mode == ControlMode.Manual
        self.qpbt_valve_1.setEnabled(flag)
        self.qpbt_valve_2.setEnabled(flag)
        self.qpbt_pump.setEnabled(flag)
        self.qpbt_burst_incr_RH.setEnabled(flag)
        self.qpbt_burst_decr_RH.setEnabled(flag)

    @QtCore.pyqtSlot(bool)
    def process_qchk_incr_ENA_valve_1(self, checked: bool):
        self.ard_qdev.config.actors_incr_RH.ENA_valve_1 = checked

    @QtCore.pyqtSlot(bool)
    def process_qchk_incr_ENA_valve_2(self, checked: bool):
        self.ard_qdev.config.actors_incr_RH.ENA_valve_2 = checked

    @QtCore.pyqtSlot(bool)
    def process_qchk_incr_ENA_pump(self, checked: bool):
        self.ard_qdev.config.actors_incr_RH.ENA_pump = checked

    @QtCore.pyqtSlot(bool)
    def process_qchk_decr_ENA_valve_1(self, checked: bool):
        self.ard_qdev.config.actors_decr_RH.ENA_valve_1 = checked

    @QtCore.pyqtSlot(bool)
    def process_qchk_decr_ENA_valve_2(self, checked: bool):
        self.ard_qdev.config.actors_decr_RH.ENA_valve_2 = checked

    @QtCore.pyqtSlot(bool)
    def process_qchk_decr_ENA_pump(self, checked: bool):
        self.ard_qdev.config.actors_decr_RH.ENA_pump = checked

    @QtCore.pyqtSlot(bool)
    def process_qrbt_act_on_sensor_1(self, checked: bool):
        self.ard_qdev.config.act_on_sensor_no = 1 if checked else 2

    @QtCore.pyqtSlot(bool)
    def process_qrbt_act_on_sensor_2(self, checked: bool):
        self.ard_qdev.config.act_on_sensor_no = 2 if checked else 1

    @QtCore.pyqtSlot()
    def process_qlin_fineband_dLO(self):
        try:
            val = float(self.qlin_fineband_dLO.text())
        except ValueError:
            val = self.ard_qdev.config.fineband_dLO

        val = -(abs(val))
        self.qlin_fineband_dLO.setText("%+.1f" % val)
        self.ard_qdev.config.fineband_dLO = val

    @QtCore.pyqtSlot()
    def process_qlin_fineband_dHI(self):
        try:
            val = float(self.qlin_fineband_dHI.text())
        except ValueError:
            val = self.ard_qdev.config.fineband_dHI

        val = max(val, 0)
        self.qlin_fineband_dHI.setText("%+.1f" % val)
        self.ard_qdev.config.fineband_dHI = val

    @QtCore.pyqtSlot()
    def process_qlin_deadband_dLO(self):
        try:
            val = float(self.qlin_deadband_dLO.text())
        except ValueError:
            val = self.ard_qdev.config.deadband_dLO

        val = -(abs(val))
        self.qlin_deadband_dLO.setText("%+.1f" % val)
        self.ard_qdev.config.deadband_dLO = val

    @QtCore.pyqtSlot()
    def process_qlin_deadband_dHI(self):
        try:
            val = float(self.qlin_deadband_dHI.text())
        except ValueError:
            val = self.ard_qdev.config.deadband_dHI

        val = max(val, 0)
        self.qlin_deadband_dHI.setText("%+.1f" % val)
        self.ard_qdev.config.deadband_dHI = val

    @QtCore.pyqtSlot()
    def process_qlin_burst_update_period(self):
        try:
            val = int(self.qlin_burst_update_period.text())
        except ValueError:
            val = self.ard_qdev.config.burst_update_period

        val = max(val, 1)
        self.qlin_burst_update_period.setText("%u" % val)
        self.ard_qdev.config.burst_update_period = val

    @QtCore.pyqtSlot()
    def process_qlin_burst_incr_RH_length(self):
        try:
            val = int(self.qlin_burst_incr_RH_length.text())
        except ValueError:
            val = self.ard_qdev.config.burst_incr_RH_length

        val = max(val, 500)
        self.qlin_burst_incr_RH_length.setText("%u" % val)
        self.ard_qdev.config.burst_incr_RH_length = val

    @QtCore.pyqtSlot()
    def process_qlin_burst_decr_RH_length(self):
        try:
            val = int(self.qlin_burst_decr_RH_length.text())
        except ValueError:
            val = self.ard_qdev.config.burst_decr_RH_length

        val = max(val, 500)
        self.qlin_burst_decr_RH_length.setText("%u" % val)
        self.ard_qdev.config.burst_decr_RH_length = val

    # --------------------------------------------------------------------------
    #   Configuration files
    # --------------------------------------------------------------------------

    def save_config_to_file(self, as_default: bool = True):
        config = self.ard_qdev.config  # Shorthand

        cp = ConfigParser()
        cp.optionxform = lambda option: option  # Preserve letter case

        descr = "Humidistat"
        cp.add_section(descr)
        tmp = config.actors_incr_RH
        cp.set(descr, "actors_incr_RH_ENA_valve_1", str(tmp.ENA_valve_1))
        cp.set(descr, "actors_incr_RH_ENA_valve_2", str(tmp.ENA_valve_2))
        cp.set(descr, "actors_incr_RH_ENA_pump", str(tmp.ENA_pump))
        tmp = config.actors_decr_RH
        cp.set(descr, "actors_decr_RH_ENA_valve_1", str(tmp.ENA_valve_1))
        cp.set(descr, "actors_decr_RH_ENA_valve_2", str(tmp.ENA_valve_2))
        cp.set(descr, "actors_decr_RH_ENA_pump", str(tmp.ENA_pump))
        cp.set(descr, "act_on_sensor_no", str(config.act_on_sensor_no))
        cp.set(descr, "fineband_dHI", str(config.fineband_dHI))
        cp.set(descr, "fineband_dLO", str(config.fineband_dLO))
        cp.set(descr, "deadband_dHI", str(config.deadband_dHI))
        cp.set(descr, "deadband_dLO", str(config.deadband_dLO))
        cp.set(descr, "burst_update_period", str(config.burst_update_period))
        cp.set(descr, "burst_incr_RH_length", str(config.burst_incr_RH_length))
        cp.set(descr, "burst_decr_RH_length", str(config.burst_decr_RH_length))

        if as_default:
            fn = DEFAULT_CONFIG_FILE
        else:  # Ask user for filename
            suggested_name = (
                "humidistat_config_%s.ini"
                % QDateTime.currentDateTime().toString("yyMMdd_HHmmss")
            )
            options = QFileDialog.Options()
            # options |= QFileDialog.DontUseNativeDialog
            fn, _ = QFileDialog.getSaveFileName(
                self,
                caption="Save Humidistat configuration to file",
                directory=suggested_name,
                filter="Configuration files (*.ini);;All Files (*)",
                options=options,
            )
            if not fn:
                return

        fn = Path(fn)
        try:
            with open(fn, "w") as f:
                cp.write(f)
        except Exception as err:  # pylint: disable=broad-except
            dprint("ERROR: Failed to write configuration to file")
            pft(err)
        else:
            dprint("Succesfully saved configuration file: %s" % fn)

    def load_config_from_file(self, from_default=True):
        config = self.ard_qdev.config  # Shorthand

        if from_default:
            fn = DEFAULT_CONFIG_FILE
        else:  # Ask user for filename
            options = QFileDialog.Options()
            # options |= QFileDialog.DontUseNativeDialog
            fn, _ = QFileDialog.getOpenFileName(
                self,
                caption="Load Humidistat configuration from file",
                directory="",
                filter="Configuration files (*.ini);;All Files (*)",
                options=options,
            )
            if not fn:
                return

        fn = Path(fn)
        cp = ConfigParser()
        try:
            cp.read(fn)
        except Exception as err:  # pylint: disable=broad-except
            dprint("ERROR: Failed to load configuration from file")
            pft(err)
            return

        try:
            descr = "Humidistat"
            config.actors_incr_RH.ENA_valve_1 = cp.getboolean(
                descr, "actors_incr_RH_ENA_valve_1"
            )
            config.actors_incr_RH.ENA_valve_2 = cp.getboolean(
                descr, "actors_incr_RH_ENA_valve_2"
            )
            config.actors_incr_RH.ENA_pump = cp.getboolean(
                descr, "actors_incr_RH_ENA_pump"
            )
            config.actors_decr_RH.ENA_valve_1 = cp.getboolean(
                descr, "actors_decr_RH_ENA_valve_1"
            )
            config.actors_decr_RH.ENA_valve_2 = cp.getboolean(
                descr, "actors_decr_RH_ENA_valve_2"
            )
            config.actors_decr_RH.ENA_pump = cp.getboolean(
                descr, "actors_decr_RH_ENA_pump"
            )
            config.act_on_sensor_no = cp.getint(descr, "act_on_sensor_no")
            config.fineband_dHI = cp.getfloat(descr, "fineband_dHI")
            config.fineband_dLO = cp.getfloat(descr, "fineband_dLO")
            config.deadband_dHI = cp.getfloat(descr, "deadband_dHI")
            config.deadband_dLO = cp.getfloat(descr, "deadband_dLO")
            config.burst_update_period = cp.getint(descr, "burst_update_period")
            config.burst_incr_RH_length = cp.getint(
                descr, "burst_incr_RH_length"
            )
            config.burst_decr_RH_length = cp.getint(
                descr, "burst_decr_RH_length"
            )
        except Exception as err:  # pylint: disable=broad-except
            dprint("ERROR: Failed to load configuration from file")
            pft(err)
        else:
            dprint("Succesfully loaded configuration file: %s" % fn)
            self.populate_configuration()


# -*- coding: utf-8 -*-
"""
Created on Wed May 20 13:02:06 2020

@author: srinivasan.c
"""

from keras.preprocessing.image import ImageDataGenerator, array_to_img,img_to_array,load_img
import tensorflow as tf

datagen = ImageDataGenerator(
        rotation_range=180,
        width_shift_range = 0.2,
        height_shift_range = 0.2,
        shear_range = 0.2,
        zoom_range = 0.2,
        horizontal_flip = True,
        fill_mode = 'nearest'
        )
img = load_img('D:\python\data\image\dog1.jpg')
x = img_to_array(img)
x = x.reshape((1,) + x.shape)

file_name = 'dog_' #filename prefix
i = 1
format_type = 'jpg' # format type for generated image
save_dir = 'D:/python/CNN/test_data/dog/'
for batch in datagen.flow(x, batch_size = 1,
                           save_to_dir = save_dir, save_prefix = 'dog',
                           save_format = format_type):
    i += 1
    if i > 70:
        break

import os
os.getcwd()
collection = save_dir
for i, filename in enumerate(os.listdir(collection)):
    i+=1
    os.rename(save_dir + filename, save_dir + file_name + str(i) + "."+format_type)


import json
import logging

from airflow.operators.sensors import BaseSensorOperator
from airflow.utils.decorators import apply_defaults

from airflow_xplenty.client_factory import ClientFactory

class XplentyWaitForJobSensor(BaseSensorOperator):
    """Wait for a job to finish
    """

    SUCCESS_STATUSES = ["completed"]
    FAILED_STATUSES = ["failed", "stopped"]

    @apply_defaults
    def __init__(self, start_job_task_id, **kwargs):
        self.start_job_task_id = start_job_task_id
        self.client = ClientFactory().client()

        super(XplentyWaitForJobSensor, self).__init__(**kwargs)

    def poke(self, context):
        job_id = context["task_instance"].xcom_pull(task_ids=self.start_job_task_id)
        if job_id is None:
            raise Exception("No job_id found in XComs")

        job = self.client.get_job(job_id)
        if job.status in self.FAILED_STATUSES:
            raise Exception("Job failed: %s" % job.errors)
        elif job.status in self.SUCCESS_STATUSES:
            logging.info("Job %d finished in state %s", job_id, job.status)
            logging.info(json.dumps(job.outputs, indent=4))
            context["task_instance"].xcom_push(
                key="xplenty_job_outputs", value=job.outputs
            )
            return True
        else:
            progress = round(job.progress * 100, 1)
            logging.info("Job %d in state %s (%.1f%%)", job_id, job.status, progress)
            return False


from ai_dungeon.storage.user_info import get_user_info, insert_action
from ai_dungeon.translations.yandex import translate
from aidungeonapi import AIDungeonClient, AIDungeonAdventure
from kutana import Plugin, Message

plugin = Plugin(name="Actions")

async def process_actions(ctx, adventure_id, actions, skip_read=True):
    """
        Iterates over actions. Prints newly added actions. Saves to the app storage.
    """
    user_info = await get_user_info(ctx)
    new_actions = []
    for action in actions:
        added = await insert_action(ctx, adventure_id, action)
        if added or not skip_read:
            new_actions.append(action)
    text = ''.join([action['text'] for action in new_actions])
    if text:
        await ctx.reply(await translate(text, "en", user_info.language))
    await ctx.set_state(user_state='game')

@plugin.on_commands(commands=['do', 'say', 'story'], user_state='game')
async def _(msg: Message, ctx):
    """
        Handles /do, /say and /story.
        If died, returns to 'ready' state.
    """
    user_info = await get_user_info(ctx)

    client = await AIDungeonClient(token=user_info.token or '', debug=True)
    adventure = await AIDungeonAdventure(client, id=user_info.adventure)

    await adventure.send_text(await translate(ctx.body, user_info.language, "en"), ctx.command)

    await process_actions(ctx, adventure.id, await adventure.obtain_actions())

    if await adventure.obtain_has_died():
        await ctx.set_state(user_state='ready')
        await ctx.reply((await translate("Game over. To start again, send: ", "en", user_info.language)) + "/play")

@plugin.on_commands(commands=['cancel'])
async def _(msg: Message, ctx):
    """
        Command to stop history.
    """
    user_info = await get_user_info(ctx)
    # Avoid skipping language entry
    if ctx.user_state and ctx.user_state != 'language':
        await ctx.set_state(user_state='ready')
        await ctx.reply(await translate("Game over", "en", user_info.language))

@plugin.on_any_unprocessed_message(user_state='game')
async def _(message: Message, ctx):
    user_info = await get_user_info(ctx)
    await ctx.reply(
        (await translate("Бот не знает, как обработать ваше сообщение! Попробуйте использовать одну из команд: ", "ru",
                         user_info.language)) +
        "\n/say <text>, \n/do <text>, \n/story <text>\n" +
        (await translate("Или перезапустить игру при помощи команды ", "ru", user_info.language)) + "/start")

@plugin.on_commands(commands=['undo', 'redo', 'retry'], user_state='game')
async def _(message: Message, ctx):
    user_info = await get_user_info(ctx)

    client = await AIDungeonClient(token=user_info.token or '', debug=True)
    adventure = await AIDungeonAdventure(client, id=user_info.adventure)

    await adventure.send_simple_action(ctx.command)

    await ctx.reply(await translate((await adventure.obtain_last_action())['text'], "en", user_info.language))

@plugin.on_commands(commands=['full_story'], user_state='game')
async def _(message: Message, ctx):
    user_info = await get_user_info(ctx)

    client = await AIDungeonClient(token=user_info.token or '', debug=True)
    adventure = await AIDungeonAdventure(client, id=user_info.adventure)

    await process_actions(ctx, adventure.id, await adventure.obtain_actions(), skip_read=False)

@plugin.on_commands(commands=['last'], user_state='game')
async def _(message: Message, ctx):
    user_info = await get_user_info(ctx)

    client = await AIDungeonClient(token=user_info.token or '', debug=True)
    adventure = await AIDungeonAdventure(client, id=user_info.adventure)

    await ctx.reply(await translate((await adventure.obtain_last_action())['text'], "en", user_info.language))


# -*- coding: cp1252 -*-
import  unittest as u
from should_dsl import should, should_not

class Numero_complexo(object):
    def __init__(self, real, imaginaria):
        if(type(real)!=dict and type(real)!=list and type(real)!=str and type(real)!=set and type(imaginaria)!=dict and type(imaginaria)!=list and type(imaginaria)!=str and type(imaginaria)!=set):
            self.real=real
            self.imaginaria=imaginaria
        else:
            raise TypeError("Parâmetro não é um número!")

    def get_real(self):
        return self.real
    def get_imaginaria(self):
        return self.imaginaria

    def set_real(self,real):
        if(type(real)!=dict and type(real)!=list and type(real)!=str and type(real)!=set):
            self.real=real
        else:
            raise TypeError("Parâmetro não é um número!")

    def set_imaginaria(self,imaginaria):
        if (type(imaginaria)!=dict and type(imaginaria)!=list and type(imaginaria)!=str and type(imaginaria)!=set):
            self.imaginaria=imaginaria
        else:
            raise TypeError("Parâmetro não é um número!")

    def representa(self, q_casasDecimais):
        k='%i'%(q_casasDecimais)
        formatter="%."+k+"f"
        a=formatter%(self.real)
        b=formatter%(self.imaginaria)

        if(self.imaginaria>=0):
            return a+'+'+b+'i'
        else:
            return a+b+'i'

    def adicionar(self, onc):
        r_real=self.real+onc.get_real()
        r_imaginaria=self.imaginaria+onc.get_imaginaria()
        r=Numero_complexo(r_real,r_imaginaria)
        return r

    def subtrair (self, onc):
        r_real=self.real-onc.get_real()
        r_imaginaria=self.imaginaria-onc.get_imaginaria()
        r=Numero_complexo(r_real,r_imaginaria)
        return r

    def multiplicar(self, onc):
        r_real=(self.real*onc.get_real())-(self.imaginaria*onc.get_imaginaria())
        r_imaginaria=(self.imaginaria*onc.get_real())+(self.real*onc.get_imaginaria())
        r=Numero_complexo(r_real,r_imaginaria)
        return r

    def dividir(self, onc):
        div=(onc.get_real()*onc.get_real())+(onc.get_imaginaria()*onc.get_imaginaria())
        r_real=(self.real*onc.get_real())+(self.imaginaria*onc.get_imaginaria())/float(div)
        r_imaginaria=(self.imaginaria*onc.get_real())-(self.real*onc.get_imaginaria())/float(div)
        r=Numero_complexo(float(r_real),float(r_imaginaria))
        return r

class TesteNumero_complexo(u.TestCase):
    def test_inicializacao(self):
        q=Numero_complexo(3,8)
        q.real | should | equal_to(3)
        q.imaginaria | should | equal_to(8)

    def test_gettersAndSetters(self):
        q=Numero_complexo(3,8)
        q.set_real(-12)
        q.get_real() | should | equal_to(-12)
        q.set_imaginaria(24)
        q.get_imaginaria() | should | equal_to(24)

    def test_representa(self):
        q=Numero_complexo(3,-8)
        q.representa(2) | should | equal_to('3.00-8.00i')

    def test_adicionar(self):
        q=Numero_complexo(4,9)
        w=Numero_complexo(3,-8)
        q.adicionar(w).get_real()  | should | equal_to(7)
        q.adicionar(w).get_imaginaria() |  should | equal_to(1)

    def test_subtrair(self):
        q=Numero_complexo(4,9)
        w=Numero_complexo(3,-8)
        q.subtrair(w).get_real()  | should | equal_to(1)
        q.subtrair(w).get_imaginaria() |  should | equal_to(17)

    def test_multiplicar(self):
        q=Numero_complexo(5,1)
        w=Numero_complexo(2,-1)
        q.multiplicar(w).get_real()  | should | equal_to(11)
        q.multiplicar(w).get_imaginaria() |  should | equal_to(-3)

    def test_dividir(self):
        q=Numero_complexo(3,2)
        w=Numero_complexo(0,4)
        q.dividir(w).get_real()  | should | equal_to(0.5)
        q.dividir(w).get_imaginaria() |  should | equal_to(-0.75)

    def test_validationParameters(self):
        (Numero_complexo, "a", 1) | should | throw(TypeError, message="Parâmetro não é um número!")
        (Numero_complexo, [1,2], 1) | should | throw(TypeError, message="Parâmetro não é um número!")
        (Numero_complexo, {1,2}, 1) | should | throw(TypeError, message="Parâmetro não é um número!")

        q=Numero_complexo(1,1)
        (q.set_real, "a") | should | throw(TypeError, message="Parâmetro não é um número!")
        (q.set_real, [1,2]) | should | throw(TypeError, message="Parâmetro não é um número!")
        (q.set_real, {1,2}) | should | throw(TypeError, message="Parâmetro não é um número!")

        (q.set_imaginaria, "a") | should | throw(TypeError, message="Parâmetro não é um número!")
        (q.set_imaginaria, [1,2]) | should | throw(TypeError, message="Parâmetro não é um número!")
        (q.set_imaginaria, {1,2}) | should | throw(TypeError, message="Parâmetro não é um número!")

if(__name__=='__main__'):
    u.main()


import gpflow
import numpy as np
import matplotlib.pyplot as plt

from GaussianProcessTools import constraints

def f(x):
    return x**2

def main():

    #sample the objective functions
    n_initial = 4
    X0 = np.random.uniform(-2,2,size = (n_initial,1))
    Y0 = f(X0)

    X = X0
    Y = Y0

    #define kernels to be used for the gaussian process regressors
    kernel = gpflow.kernels.RBF(lengthscales = 0.5, variance = 0.5)

    GPR = gpflow.models.GPR((X,Y),kernel, noise_variance = 0.0001)

    constraint = constraints.Constraint(GPR,1,True)

    x = np.linspace(-5,5,100).reshape(-1,1)

    const_satisfied = constraint.predict(x)

    fig,ax = plt.subplots()
    mu,var = GPR.predict_f(x)

    mu = mu.numpy().flatten()
    sig = np.sqrt(var.numpy().flatten())
    ax.plot(x,mu)
    ax.fill_between(x.flatten(),mu-sig,mu+sig, alpha=0.25, lw = 0)

    ax.plot(x,const_satisfied)

main()
plt.show()


t = int(input())

j = 1

while j <= t:

    string = input()

    d = len(string)

    if d > 10:

        c = len(string[1:d-1])
        print(f"{string[0]}{c}{string[d-1]}")

    else:
        print(string)

    j = j + 1


from apiclient import errors

from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.core import properties
from googlecloudsdk.core.util import console_io
from googlecloudsdk.dns import util

class Create(base.Command):
  """Create a new Cloud DNS managed zone."""

  @staticmethod
  def Args(parser):
    """Args is called by calliope to gather arguments for this command.

    Args:
      parser: An argparse parser that you can use it to add arguments that go
          on the command line after this command. Positional arguments are
          allowed.
    """
    parser.add_argument(
        'zone',
        help='Managed Zone name.')
    parser.add_argument(
        '--description',
        required=True,
        help='Human readable description of this zone.  Optional.')

    parser.add_argument(
        '--dns_name',
        required=True,
        help='A domain name spec, for example "foo.bar.com.".')

  def Run(self, args):
    """Run 'dns managed-zone create'.

    Args:
      args: argparse.Namespace, The arguments that this command was invoked
          with.
    Returns:
      A dict object representing the changes resource obtained by the create
      operation if the create was successful.
    """
    project = properties.VALUES.core.project.Get(required=True)
    zone = {}
    zone['dnsName'] = args.dns_name
    zone['name'] = args.zone
    zone['description'] = args.description

    really = console_io.PromptContinue('Creating %s in %s' % (zone, project))
    if not really:
      return

    dns = self.context['dns']
    request = dns.managedZones().create(project=project, body=zone)
    try:
      result = request.execute()
      return result
    except errors.HttpError as error:
      raise exceptions.HttpException(util.GetError(error))
    except errors.Error as error:
      raise exceptions.ToolException(error)

  def Display(self, unused_args, result):
    """Display prints information about what just happened to stdout.

    Args:
      unused_args: The same as the args in Run.
      result: The results of the Run() method.
    """
    util.PrettyPrint(result)


import sys
sys.path.append('Utils/Visualization')

import FunctionsVisualization as img

def falsePosition(fx, lowerLimit, upperLimit):
    if (fx(lowerLimit)*fx(upperLimit) < 0 and lowerLimit+upperLimit != 0):
        c = 0
        while True:
            buff = c
            # perbedaan dengan metode biseksi terletak pada penentuan nilai c
            c = upperLimit - (fx(upperLimit)*(lowerLimit-upperLimit)
                              )/(fx(lowerLimit)-fx(upperLimit))
            if (fx(upperLimit)*fx(c) < 0):
                lowerLimit = c
            else:
                upperLimit = c

            if (abs((c-buff)/c) < (0.001)):
                break
        print("Akar persamaannya adalah %f" % c)
    else:
        print("Batasan yang dimasukkan salah!")

contohFungsi = lambda x: x**3 - 2*(x**2) + 6*x - 4.0

img.visualization2Variable(
    [0.6, 1.5, 0.05],
    contohFungsi,
    "Metode False Position",
    "Metnum/Akar Persamaan Non-Linear/Metode False Position/",
    "metode-false-position.png"
)

a = float(input("Masukkan batas bawah: "))
b = float(input("Masukkan batas atas : "))

falsePosition(contohFungsi, a, b)


#@+leo-ver=5-thin
#@+node:tbrown.20171028115143.3: * @file ../plugins/editpane/vanillascintilla.py
"""
vanillascintilla.py - a LeoEditPane editor that uses QScintilla
but does not try to add Leo key handling

Terry Brown, Terry_N_Brown@yahoo.com, Sat Feb  4 12:38:26 2017
"""
#@+<< vanillascintilla imports >>
#@+node:tbrown.20171028115501.1: ** << vanillascintilla imports >>
from leo.core import leoGlobals as g
assert g
from leo.core.leoQt import QtGui, QtWidgets, Qsci

if Qsci is None:  # leo.core.leoQt eats ImportErrors
    raise ImportError
#@-<< vanillascintilla imports >>
#@+others
#@+node:tbrown.20171028115501.2: ** DBG
def DBG(text):
    """DBG - temporary debugging function

    Args:
        text (str): text to print
    """
    print(f"LEP: {text}")
#@+node:tbrown.20171028115501.3: ** class LEP_VanillaScintilla
class LEP_VanillaScintilla(Qsci.QsciScintilla):
    lep_type = "EDITOR"
    lep_name = "Vanilla Scintilla"
    #@+others
    #@+node:tbrown.20171028115501.4: *3* __init__
    def __init__(self, c=None, lep=None, *args, **kwargs):
        """set up"""
        super().__init__(*args, **kwargs)
        self.c = c
        self.lep = lep
        self.textChanged.connect(self.text_changed)

        font = QtGui.QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(14)

        lexer = Qsci.QsciLexerPython()
        lexer.setDefaultFont(font)
        self.setLexer(lexer)
        # self.SendScintilla(Qsci.QsciScintilla.SCI_STYLESETFONT, 1, 'Courier')

        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QtGui.QColor("#ffe4e4"))
    #@+node:tbrown.20171028115501.5: *3* focusInEvent
    def focusInEvent(self, event):
        Qsci.QsciScintilla.focusInEvent(self, event)
        DBG("focusin()")
        self.lep.edit_widget_focus()
    #@+node:tbrown.20171028115501.6: *3* focusOutEvent
    def focusOutEvent(self, event):
        Qsci.QsciScintilla.focusOutEvent(self, event)
        DBG("focusout()")
    #@+node:tbrown.20171028115501.7: *3* new_text
    def new_text(self, text):
        """new_text - update for new text

        Args:
            text (str): new text
        """
        self.setText(text)
    #@+node:tbrown.20171028115501.8: *3* text_changed
    def text_changed(self):
        """text_changed - text editor text changed"""
        if QtWidgets.QApplication.focusWidget() == self:
            DBG("text changed, focused")
            self.lep.text_changed(self.text())
        else:
            DBG("text changed, NOT focused")
    #@+node:tbrown.20171028115501.9: *3* update_text
    def update_text(self, text):
        """update_text - update for current text

        Args:
            text (str): current text
        """
        DBG("update editor text")
        self.setText(text)
    #@-others
#@-others
#@@language python
#@@tabwidth -4
#@-leo


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
from SqlControl import SqlControl
import pandas as pd
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import HashingVectorizer
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans, MiniBatchKMeans

def loadDataset():
    '''导入文本数据集'''
    sql = SqlControl()
    job_list_with_tunple = sql.process_item('job_require')
    sql.kill_sql()
    job_list = []
    for x in job_list_with_tunple:
        job_list.append(x[0])
    after_tf = []
    for x in job_list:
        #temp = [x]
        #temp.append(jieba.analyse.extract_tags(x, topK=3, allowPOS=("n", "v")))
        temp=jieba.cut(x)
        f_temp=[]
        bad_dict=["专家","视频","初级","项目","实习生","国际","阿里","广州","经理","总监","主管","工程师","高级","大","资深","专员","方向","(",")","/","java","hadoop","spark","mysql"]
        for x in temp:
            if x not in bad_dict:
                f_temp.append(x)
                f_temp.append(" ")
        after_tf.append("".join(f_temp))
        #after_tf.append(",")
    #dataset="".join(after_tf)
    dataset=after_tf
    return dataset

def transform(dataset, n_features=1000):
    vectorizer = TfidfVectorizer(max_df=0.5, max_features=n_features, min_df=3, use_idf=True)
    X = vectorizer.fit_transform(dataset)
    return X, vectorizer

def train(X, vectorizer, true_k=1, minibatch=False, showLable=False):
    # 使用采样数据还是原始数据训练k-means，
    if minibatch:
        km = MiniBatchKMeans(n_clusters=true_k, init='k-means++', n_init=1,
                             init_size=1000, batch_size=1000, verbose=False)
    else:
        km = KMeans(n_clusters=true_k, init='k-means++', max_iter=300, n_init=1,
                    verbose=False)
    km.fit(X)
    job=[]
    if showLable:
        print("Top terms per cluster:")
        order_centroids = km.cluster_centers_.argsort()[:, ::-1]
        terms = vectorizer.get_feature_names()
        print(vectorizer.get_stop_words())
        for i in range(true_k):
            print("Cluster %d:" % i, end='')
            for ind in order_centroids[i, :3]:
                print(' %s' % terms[ind], end='')
                job.append(terms[ind])
            print()
    result = list(km.predict(X))
    print('Cluster distribution:')
    print(dict([(i, result.count(i)) for i in result]))
    #return  -km.score(X)
    return result,-km.score(X)

def test():
    '''测试选择最优参数'''
    dataset = loadDataset()
    print("%d documents" % len(dataset))
    X, vectorizer = transform(dataset, n_features=500)
    true_ks = []
    scores = []
    for i in range(3, 15, 1):
        score = train(X, vectorizer, true_k=i) / len(dataset)
        print(i, score)
        true_ks.append(i)
        scores.append(score)
    plt.figure(figsize=(8, 4))
    plt.plot(true_ks, scores, label="error", color="red", linewidth=1)
    plt.xlabel("n_features")
    plt.ylabel("error")
    plt.legend()
    plt.show()

def out():
    '''在最优参数下输出聚类结果'''
    dataset = loadDataset()
    X, vectorizer = transform(dataset, n_features=500)
    # result,score = train(X, vectorizer, true_k=8, showLable=True) / len(dataset)
    # print(score)
    result=train(X,vectorizer,true_k=16,showLable=True,minibatch=False)
    print(result)

#test()
out()


# chelsea_nayan, UWPCE Python 210, Lesson04: Kata Fourteen: Tom Swift Under Milk Wood
# This was difficult!

import random # Used to pick a random word from the dictionary value list
import re # Used to use findall()

story_limit = 10000 # Sets the character limit so the new_story can't go on forever...
text_file = 'sherlock_big.txt' # Chooses the text file to be read
first_two_words = ["One", "night"] # Chooses the first two words for the trigram

with open(text_file, 'r') as infile, open('new_story.txt', 'w') as outfile:

    text = infile.read() # Stores the txt file as a string
    lst = re.findall(r"[\w']+|[.,!?;]", text) # Creates a list of words, findall() separates the punctuation into their own 'words'

    for word in lst: # splits the words that are connected with '--'
        if "--" in word:
            sub = word.split("--")
            if sub[0] not in lst: # Add spliited words to lst if not there
                lst.append(sub[0])
            if sub[1] not in lst:
                lst.append(sub[1])
            lst.remove(word) # Removes the original word connected by '--'

    # The key is a string of two words, the value is a list of words that appear after those two words appear
    d = {}
    for elem in range(len(lst)-2):
        k = f"{lst[elem]} {lst[elem+1]}"
        if k in d.keys(): # Adds the next word to the value list
            d.get(k).append(lst[elem+2])
        else:
            d[k] = [lst[elem+2]] # Adds the new key and value (type list) to the dictionary

    two_words_list = first_two_words # Chooses the first two words for the trigram
    two_words_string = f"{two_words_list[0]} {two_words_list[1]}" # Makes the first two words into a string
    story = two_words_string
    while two_words_string in d.keys() and len(story)<story_limit: # Loops as long as the two word string is a key in the d and doesn't exceed the char story_limit
        next_word = random.choice(d.get(two_words_string)) # Get the next word from the value
        if next_word in "|[.,!?;]": # The case of the next 'word' is punctuation
            story += f"{next_word}"
        else: # The case where the next word is not punctuation
            story += f" {next_word}"
        two_words_list = [two_words_list[1], next_word]
        two_words_string = f"{two_words_list[0]} {two_words_list[1]}"

    # Makes sure the story ends with proper punctuation! Not the greatest way to simulate language...
    if two_words_list[1] not in "!?.":
        story += random.choice([".", "!", "?"])

    outfile.write(story)


with open('in.txt', 'r') as file:
	data = file.readlines()

knots = []

for i in range(10):
	knots.append([4, 0])

def is_touching(a, b):
	if knots[b][0] == knots[a][0]:
		if knots[b][1] + 1 == knots[a][1]:
			return True
		if knots[b][1] - 1 == knots[a][1]:
			return True
	if knots[b][1] == knots[a][1]:
		if knots[b][0] + 1 == knots[a][0]:
			return True
		elif knots[b][0] - 1 == knots[a][0]:
			return True

	test_diagonals = [(+1, +1), (+1, -1), (-1, +1), (-1, -1)]

	for i, j in test_diagonals:
		if knots[b][0] + i == knots[a][0] and knots[b][1] + j == knots[a][1]:
			return True

	if knots[b] == knots[a]:
		return True
	return False

def make_diagonal(a, b):
	test_diagonals = [(+1, +1), (+1, -1), (-1, +1), (-1, -1)]

	for i, j  in test_diagonals:
		knots[b][0] += i
		knots[b][1] += j

		if is_touching(a, b):
			return

		knots[b][0] -= i
		knots[b][1] -= j

def make_move(a, b):
	if knots[b][0] == knots[a][0]:
		if knots[b][1] + 2 == knots[a][1]:
			knots[b][1] += 1
			return
		elif knots[b][1] - 2 == knots[a][1]:
			knots[b][1] -= 1
			return
	if knots[b][1] == knots[a][1]:
		if knots[b][0] + 2 == knots[a][0]:
			knots[b][0] += 1
			return
		elif knots[b][0] - 2 == knots[a][0]:
			knots[b][0] -= 1
			return
	make_diagonal(a, b)

positions = set()

for line in data:
	line = line.strip('\n').split(' ')
	direction, amount = line
	amount = int(amount)

	for i in range(amount):
		if direction == 'L':
			knots[0][1] -= 1
		elif direction == 'R':
			knots[0][1] += 1
		elif direction == 'U':
			knots[0][0] -= 1
		elif direction == 'D':
			knots[0][0] += 1

		for j in range(1, 10):
			if not is_touching(j-1, j):
				make_move(j-1, j)

		positions.add(tuple(knots[-1]))

print(len(positions))


import logging
from prelogging import LCDict

_level_count = 0

def filter_fn(record, **kwargs):
    """ Returns int or bool -- not great practice, but just to distinguish
    which branch of if-then-else was taken.
    """
    filtername = kwargs.get('filtername', '')
    loglevel_to_count = kwargs.get('loglevel_to_count', 0)

    """Suppress odd-numbered messages (records)
    whose level == loglevel_to_count,
    where the "first" message is 0-th hence even-numbered.
    """
    global _level_count
    if record.levelno == loglevel_to_count:
        _level_count += 1
        ret = _level_count % 2      # int
    else:
        ret = True                  # bool

    print("{}: record levelname = {}, _level_count = {}; returning {}".
          format(filtername, record.levelname,
                 _level_count, ret))
    return ret

def config_logging():
    lcd = LCDict(attach_handlers_to_root=True,
                 root_level='DEBUG')
    lcd.add_stdout_handler('console-out',
                           level='DEBUG',
                           formatter='level_msg')
    lcd.add_callable_filter('count_info', filter_fn,
                            # extra, static data
                            filtername='count_info',
                            loglevel_to_count=logging.INFO)
    lcd.attach_root_filters('count_info')

    lcd.config()

if __name__ == '__main__':
    config_logging()

    root = logging.getLogger()

    for i in range(2):
        print("\ni ==", i)
        root.debug(str(i))
        root.info(str(i))


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

"""This is a awesome
    python script...!"""

import os
import sys
import time
import sysv_ipc
import threading

key = 999

def worker(mq, m):
  print("Starting thread:", threading.current_thread().name)
  datetime = time.asctime()
  message = str(datetime).encode() # encode current time
  pid = int(m.decode()) # decode the PID of the thread client (send by the client)
  t = pid + 3 # unique type of message queue, is for the client, like this he can filter his own msg
  time.sleep(5) # just sleep to look behavior with several client
  mq.send(message, type=t) # put in mailbox the current datetime and type of the msg who is PID+3
  print("Ending thread:", threading.current_thread().name)

if __name__ == "__main__":
  print("Starting thread:", threading.current_thread().name)

  try:
    mq = sysv_ipc.MessageQueue(key, sysv_ipc.IPC_CREX) # create a mailbox/tube
  except ExistentialError:
    print("Message queue", key, "already exist, terminating.")
    sys.exit(1)

  print("Starting time server.")

  threads = []

  while True:
      m, t = mq.receive()
      if t == 1:
        p = threading.Thread(target=worker, args=(mq, m)) # create a thread worker to execute the datetime cmd. We have one thread for each client, which isn't secure. Because we can overflow the server
        p.start()
        threads.append(p) # create a list of the current worker thread like this we can wait all worker
      if t == 2:
        print("Shutdown Server.")
        for thread in threads:
          thread.join() # we wait all current worker to finish
        mq.remove() # delete the mailbox/tube
        break
  print("Terminating time server.")
  print("Ending thread:", threading.current_thread().name)


from django.urls import path
from main import views

app_name = 'main'
urlpatterns = [
    path('', views.home, name='home'),
    path('music/', views.music_home, name='music_home'),
    path('adminlinks/', views.admin_links, name="admin_links"),
    path('dbjson/', views.dbjson, name="dbjson"),
]