#!/usr/bin/python
from collections import OrderedDict

from Qt import QtGui, QtCore, QtWidgets

from NodeGraphQt.qgraphics.node_abstract import AbstractNodeItem
from NodeGraphQt.qgraphics.node_overlay_disabled import XDisabledItem
from NodeGraphQt.qgraphics.node_text_item import NodeTextItem
from NodeGraphQt.qgraphics.port import PortItem, CustomPortItem
from NodeGraphQt.constants import (IN_PORT, OUT_PORT,
                         NODE_WIDTH, NODE_HEIGHT,
                         NODE_ICON_SIZE, ICON_NODE_BASE,
                         NODE_SEL_COLOR, NODE_SEL_BORDER_COLOR,
                         PORT_FALLOFF, Z_VAL_NODE,
                         ITEM_CACHE_MODE)
from NodeGraphQt.errors import NodeWidgetError

class NodeItem(AbstractNodeItem):
    """
    Base Node item.

    Args:
        name (str): name displayed on the node.
        parent (QtWidgets.QGraphicsItem): parent item.
    """

    def __init__(self, name='node', parent=None):
        super(NodeItem, self).__init__(name, parent)
        pixmap = QtGui.QPixmap(ICON_NODE_BASE)
        if pixmap.size().height() > NODE_ICON_SIZE:
            pixmap = pixmap.scaledToHeight(NODE_ICON_SIZE,
                                           QtCore.Qt.SmoothTransformation)
        self._properties['icon'] = ICON_NODE_BASE
        self._icon_item = QtWidgets.QGraphicsPixmapItem(pixmap, self)
        self._icon_item.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self._text_item = NodeTextItem(self.name, self)
        self._x_item = XDisabledItem(self, 'DISABLED')
        self._input_items = OrderedDict()
        self._output_items = OrderedDict()
        self._widgets = OrderedDict()
        self._proxy_mode = False
        self._proxy_mode_threshold = 70

    def paint(self, painter, option, widget):
        """
        Draws the node base not the ports.

        Args:
            painter (QtGui.QPainter): painter used for drawing the item.
            option (QtGui.QStyleOptionGraphicsItem):
                used to describe the parameters needed to draw.
            widget (QtWidgets.QWidget): not used.
        """
        self.auto_switch_mode()

        painter.save()
        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(QtCore.Qt.NoBrush)

        # base background.
        margin = 1.0
        rect = self.boundingRect()
        rect = QtCore.QRectF(rect.left() + margin,
                             rect.top() + margin,
                             rect.width() - (margin * 2),
                             rect.height() - (margin * 2))

        radius = 4.0
        painter.setBrush(QtGui.QColor(*self.color))
        painter.drawRoundedRect(rect, radius, radius)

        # light overlay on background when selected.
        if self.selected:
            painter.setBrush(QtGui.QColor(*NODE_SEL_COLOR))
            painter.drawRoundedRect(rect, radius, radius)

        # node name background.
        padding = 3.0, 2.0
        text_rect = self._text_item.boundingRect()
        text_rect = QtCore.QRectF(text_rect.x() + padding[0],
                                  rect.y() + padding[1],
                                  rect.width() - padding[0] - margin,
                                  text_rect.height() - (padding[1] * 2))
        painter.setBrush(QtGui.QColor(0, 0, 0, 50))
        if self.selected:
            painter.setBrush(QtGui.QColor(*NODE_SEL_COLOR))
        painter.drawRoundedRect(text_rect, 3.0, 3.0)

        # node border
        border_width = 0.8
        border_color = QtGui.QColor(*self.border_color)
        if self.selected:
            border_width = 1.2
            border_color = QtGui.QColor(*NODE_SEL_BORDER_COLOR)
        border_rect = QtCore.QRectF(rect.left(), rect.top(),
                                    rect.width(), rect.height())

        pen = QtGui.QPen(border_color, border_width)
        pen.setCosmetic(self.viewer().get_zoom() < 0.0)
        path = QtGui.QPainterPath()
        path.addRoundedRect(border_rect, radius, radius)
        painter.setBrush(QtCore.Qt.NoBrush)
        painter.setPen(pen)
        painter.drawPath(path)

        painter.restore()

    def mousePressEvent(self, event):
        """
        Re-implemented to ignore event if LMB is over port collision area.

        Args:
            event (QtWidgets.QGraphicsSceneMouseEvent): mouse event.
        """
        if event.button() == QtCore.Qt.LeftButton:
            for p in self._input_items.keys():
                if p.hovered:
                    event.ignore()
                    return
            for p in self._output_items.keys():
                if p.hovered:
                    event.ignore()
                    return
        super(NodeItem, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        """
        Re-implemented to ignore event if Alt modifier is pressed.

        Args:
            event (QtWidgets.QGraphicsSceneMouseEvent): mouse event.
        """
        if event.modifiers() == QtCore.Qt.AltModifier:
            event.ignore()
            return
        super(NodeItem, self).mouseReleaseEvent(event)

    def mouseDoubleClickEvent(self, event):
        """
        Re-implemented to emit "node_double_clicked" signal.

        Args:
            event (QtWidgets.QGraphicsSceneMouseEvent): mouse event.
        """
        if event.button() == QtCore.Qt.LeftButton:

            # enable text item edit mode.
            items = self.scene().items(event.scenePos())
            if self._text_item in items:
                self._text_item.set_editable(True)
                self._text_item.setFocus()
                event.ignore()
                return

            viewer = self.viewer()
            if viewer:
                viewer.node_double_clicked.emit(self.id)
        super(NodeItem, self).mouseDoubleClickEvent(event)

    def itemChange(self, change, value):
        """
        Re-implemented to update pipes on selection changed.

        Args:
            change:
            value:
        """
        if change == self.ItemSelectedChange and self.scene():
            self.reset_pipes()
            if value:
                self.highlight_pipes()
            self.setZValue(Z_VAL_NODE)
            if not self.selected:
                self.setZValue(Z_VAL_NODE + 1)

        return super(NodeItem, self).itemChange(change, value)

    def _tooltip_disable(self, state):
        """
        Updates the node tooltip when the node is enabled/disabled.

        Args:
            state (bool): node disable state.
        """
        tooltip = '{}'.format(self.name)
        if state:
            tooltip += ' (DISABLED)'
        tooltip += '
{}
'.format(self.type_)
        self.setToolTip(tooltip)

    def _set_base_size(self, add_w=0.0, add_h=0.0):
        """
        setup initial base size.

        Args:
            add_w (float): additional width.
            add_h (float): additional height.
        """
        width, height = self.calc_size(add_w, add_h)
        self._width = width if width > NODE_WIDTH else NODE_WIDTH
        self._height = height if height > NODE_HEIGHT else NODE_HEIGHT

    def _set_text_color(self, color):
        """
        set text color.

        Args:
            color (tuple): color value in (r, g, b, a).
        """
        text_color = QtGui.QColor(*color)
        for port, text in self._input_items.items():
            text.setDefaultTextColor(text_color)
        for port, text in self._output_items.items():
            text.setDefaultTextColor(text_color)
        self._text_item.setDefaultTextColor(text_color)

    def activate_pipes(self):
        """
        active pipe color.
        """
        ports = self.inputs + self.outputs
        for port in ports:
            for pipe in port.connected_pipes:
                pipe.activate()

    def highlight_pipes(self):
        """
        Highlight pipe color.
        """
        ports = self.inputs + self.outputs
        for port in ports:
            for pipe in port.connected_pipes:
                pipe.highlight()

    def reset_pipes(self):
        """
        Reset all the pipe colors.
        """
        ports = self.inputs + self.outputs
        for port in ports:
            for pipe in port.connected_pipes:
                pipe.reset()

    def calc_size(self, add_w=0.0, add_h=0.0):
        """
        Calculates the minimum node size.

        Args:
            add_w (float): additional width.
            add_h (float): additional height.

        Returns:
            tuple(float, float): width, height.
        """
        width = self._text_item.boundingRect().width()
        height = self._text_item.boundingRect().height()

        if self._widgets:
            wid_width = max([
                w.boundingRect().width() for w in self._widgets.values()
            ])
            if width < wid_width:
                width = wid_width

        port_height = 0.0
        if self._input_items:
            port = None
            input_widths = []
            for port, text in self._input_items.items():
                input_width = port.boundingRect().width() - PORT_FALLOFF
                if text.isVisible():
                    input_width += text.boundingRect().width() / 1.5
                input_widths.append(input_width)
            width += max(input_widths)
            if port:
                port_height = port.boundingRect().height()

        if self._output_items:
            port = None
            output_widths = []
            for port, text in self._output_items.items():
                output_width = port.boundingRect().width()
                if text.isVisible():
                    output_width += text.boundingRect().width() / 1.5
                output_widths.append(output_width)
            width += max(output_widths)
            if port:
                port_height = port.boundingRect().height()

        in_count = len([p for p in self.inputs if p.isVisible()])
        out_count = len([p for p in self.outputs if p.isVisible()])
        height += port_height * max([in_count, out_count])
        if self._widgets:
            wid_height = 0.0
            for w in self._widgets.values():
                wid_height += w.boundingRect().height()
            wid_height += wid_height / len(self._widgets.values())
            if wid_height > height:
                height = wid_height

        width += add_w
        height += add_h
        return width, height

    def align_icon(self, h_offset=0.0, v_offset=0.0):
        """
        Align node icon to the default top left of the node.

        Args:
            v_offset (float): vertical offset.
            h_offset (float): horizontal offset.
        """
        x = 2.0 + h_offset
        y = 1.0 + v_offset
        self._icon_item.setPos(x, y)

    def align_label(self, h_offset=0.0, v_offset=0.0):
        """
        Center node label text to the top of the node.

        Args:
            v_offset (float): vertical offset.
            h_offset (float): horizontal offset.
        """
        rect = self.boundingRect()
        text_rect = self._text_item.boundingRect()
        x = rect.center().x() - (text_rect.width() / 2)
        y = 0.0
        self._text_item.setPos(x + h_offset, y + v_offset)

    def align_widgets(self, v_offset=0.0):
        """
        Align node widgets to the default center of the node.

        Args:
            v_offset (float): vertical offset.
        """
        if not self._widgets:
            return
        wid_heights = sum(
            [w.boundingRect().height() for w in self._widgets.values()])
        pos_y = self._height / 2
        pos_y -= wid_heights / 2
        pos_y += v_offset
        for widget in self._widgets.values():
            rect = widget.boundingRect()
            pos_x = (self._width / 2) - (rect.width() / 2)
            widget.setPos(pos_x, pos_y)
            pos_y += rect.height()

    def align_ports(self, v_offset=0.0):
        """
        Align input, output ports in the node layout.

        Args:
            v_offset (float): port vertical offset.
        """
        width = self._width
        txt_offset = PORT_FALLOFF - 2
        spacing = 1

        # adjust input position
        inputs = [p for p in self.inputs if p.isVisible()]
        if inputs:
            port_width = inputs[0].boundingRect().width()
            port_height = inputs[0].boundingRect().height()
            port_x = (port_width / 2) * -1
            port_y = v_offset
            for port in inputs:
                port.setPos(port_x, port_y)
                port_y += port_height + spacing
        # adjust input text position
        for port, text in self._input_items.items():
            if port.isVisible():
                txt_x = port.boundingRect().width() / 2 - txt_offset
                text.setPos(txt_x, port.y() - 1.5)

        # adjust output position
        outputs = [p for p in self.outputs if p.isVisible()]
        if outputs:
            port_width = outputs[0].boundingRect().width()
            port_height = outputs[0].boundingRect().height()
            port_x = width - (port_width / 2)
            port_y = v_offset
            for port in outputs:
                port.setPos(port_x, port_y)
                port_y += port_height + spacing
        # adjust output text position
        for port, text in self._output_items.items():
            if port.isVisible():
                txt_width = text.boundingRect().width() - txt_offset
                txt_x = port.x() - txt_width
                text.setPos(txt_x, port.y() - 1.5)

    def offset_label(self, x=0.0, y=0.0):
        """
        offset the label in the node layout.

        Args:
            x (float): horizontal x offset
            y (float): vertical y offset
        """
        icon_x = self._text_item.pos().x() + x
        icon_y = self._text_item.pos().y() + y
        self._text_item.setPos(icon_x, icon_y)

    def draw_node(self):
        """
        Re-draw the node item in the scene.
        (re-implemented for vertical layout design)
        """
        height = self._text_item.boundingRect().height()

        # setup initial base size.
        self._set_base_size(add_w=0.0, add_h=height)
        # set text color when node is initialized.
        self._set_text_color(self.text_color)
        # set the tooltip
        self._tooltip_disable(self.disabled)

        # --- set the initial node layout ---
        # (do all the graphic item layout offsets here)

        # align label text
        self.align_label()
        # arrange icon
        self.align_icon()
        # arrange input and output ports.
        self.align_ports(v_offset=height + (height / 2))
        # arrange node widgets
        self.align_widgets(v_offset=height / 2)

        self.update()

    def post_init(self, viewer=None, pos=None):
        """
        Called after node has been added into the scene.
        Adjust the node layout and form after the node has been added.

        Args:
            viewer (NodeGraphQt.widgets.viewer.NodeViewer): not used
            pos (tuple): cursor position.
        """
        self.draw_node()

        # set initial node position.
        if pos:
            self.xy_pos = pos

    def auto_switch_mode(self):
        """
        Decide whether to draw the node with proxy mode.
        (this is called at the start in the "self.paint()" function.)
        """
        if ITEM_CACHE_MODE is QtWidgets.QGraphicsItem.ItemCoordinateCache:
            return

        rect = self.sceneBoundingRect()
        l = self.viewer().mapToGlobal(
            self.viewer().mapFromScene(rect.topLeft()))
        r = self.viewer().mapToGlobal(
            self.viewer().mapFromScene(rect.topRight()))
        # width is the node width in screen
        width = r.x() - l.x()

        self.set_proxy_mode(width < self._proxy_mode_threshold)

    def set_proxy_mode(self, mode):
        """
        Set whether to draw the node with proxy mode.
        (proxy mode toggles visibility for some qgraphic items in the node.)

        Args:
            mode (bool): true to enable proxy mode.
        """
        if mode is self._proxy_mode:
            return
        self._proxy_mode = mode

        visible = not mode

        # node widget visibility
        for w in self._widgets.values():
            w.widget().setVisible(visible)

        # input port text visibility.
        for port, text in self._input_items.items():
            if port.display_name:
                text.setVisible(visible)

        # output port text visibility.
        for port, text in self._output_items.items():
            if port.display_name:
                text.setVisible(visible)

        self._text_item.setVisible(visible)
        self._icon_item.setVisible(visible)

    @property
    def icon(self):
        return self._properties['icon']

    @icon.setter
    def icon(self, path=None):
        self._properties['icon'] = path
        path = path or ICON_NODE_BASE
        pixmap = QtGui.QPixmap(path)
        if pixmap.size().height() > NODE_ICON_SIZE:
            pixmap = pixmap.scaledToHeight(NODE_ICON_SIZE,
                                           QtCore.Qt.SmoothTransformation)
        self._icon_item.setPixmap(pixmap)
        if self.scene():
            self.post_init()

        self.update()

    @AbstractNodeItem.width.setter
    def width(self, width=0.0):
        w, h = self.calc_size()
        width = width if width > w else w
        AbstractNodeItem.width.fset(self, width)

    @AbstractNodeItem.height.setter
    def height(self, height=0.0):
        w, h = self.calc_size()
        h = 70 if h < 70 else h
        height = height if height > h else h
        AbstractNodeItem.height.fset(self, height)

    @AbstractNodeItem.disabled.setter
    def disabled(self, state=False):
        AbstractNodeItem.disabled.fset(self, state)
        for n, w in self._widgets.items():
            w.widget().setDisabled(state)
        self._tooltip_disable(state)
        self._x_item.setVisible(state)

    @AbstractNodeItem.selected.setter
    def selected(self, selected=False):
        AbstractNodeItem.selected.fset(self, selected)
        if selected:
            self.highlight_pipes()

    @AbstractNodeItem.name.setter
    def name(self, name=''):
        AbstractNodeItem.name.fset(self, name)
        if name == self._text_item.toPlainText():
            return
        self._text_item.setPlainText(name)
        if self.scene():
            self.align_label()
        self.update()

    @AbstractNodeItem.color.setter
    def color(self, color=(100, 100, 100, 255)):
        AbstractNodeItem.color.fset(self, color)
        if self.scene():
            self.scene().update()
        self.update()

    @AbstractNodeItem.text_color.setter
    def text_color(self, color=(100, 100, 100, 255)):
        AbstractNodeItem.text_color.fset(self, color)
        self._set_text_color(color)
        self.update()

    @property
    def text_item(self):
        """
        Get the node name text qgraphics item.

        Returns:
            NodeTextItem: node text object.
        """
        return self._text_item

    @property
    def inputs(self):
        """
        Returns:
            list[PortItem]: input port graphic items.
        """
        return list(self._input_items.keys())

    @property
    def outputs(self):
        """
        Returns:
            list[PortItem]: output port graphic items.
        """
        return list(self._output_items.keys())

    def _add_port(self, port):
        """
        Adds a port qgraphics item into the node.

        Args:
            port (PortItem): port item.

        Returns:
            PortItem: port qgraphics item.
        """
        text = QtWidgets.QGraphicsTextItem(port.name, self)
        text.font().setPointSize(8)
        text.setFont(text.font())
        text.setVisible(port.display_name)
        text.setCacheMode(ITEM_CACHE_MODE)
        if port.port_type == IN_PORT:
            self._input_items[port] = text
        elif port.port_type == OUT_PORT:
            self._output_items[port] = text
        if self.scene():
            self.post_init()
        return port

    def add_input(self, name='input', multi_port=False, display_name=True,
                  locked=False, painter_func=None):
        """
        Adds a port qgraphics item into the node with the "port_type" set as
        IN_PORT.

        Args:
            name (str): name for the port.
            multi_port (bool): allow multiple connections.
            display_name (bool): display the port name.
            locked (bool): locked state.
            painter_func (function): custom paint function.

        Returns:
            PortItem: input port qgraphics item.
        """
        if painter_func:
            port = CustomPortItem(self, painter_func)
        else:
            port = PortItem(self)
        port.name = name
        port.port_type = IN_PORT
        port.multi_connection = multi_port
        port.display_name = display_name
        port.locked = locked
        return self._add_port(port)

    def add_output(self, name='output', multi_port=False, display_name=True,
                   locked=False, painter_func=None):
        """
        Adds a port qgraphics item into the node with the "port_type" set as
        OUT_PORT.

        Args:
            name (str): name for the port.
            multi_port (bool): allow multiple connections.
            display_name (bool): display the port name.
            locked (bool): locked state.
            painter_func (function): custom paint function.

        Returns:
            PortItem: output port qgraphics item.
        """
        if painter_func:
            port = CustomPortItem(self, painter_func)
        else:
            port = PortItem(self)
        port.name = name
        port.port_type = OUT_PORT
        port.multi_connection = multi_port
        port.display_name = display_name
        port.locked = locked
        return self._add_port(port)

    def _delete_port(self, port, text):
        """
        Removes port item and port text from node.

        Args:
            port (PortItem): port object.
            text (QtWidgets.QGraphicsTextItem): port text object.
        """
        port.delete()
        port.setParentItem(None)
        text.setParentItem(None)
        self.scene().removeItem(port)
        self.scene().removeItem(text)
        del port
        del text

    def delete_input(self, port):
        """
        Remove input port from node.

        Args:
            port (PortItem): port object.
        """
        self._delete_port(port, self._input_items.pop(port))

    def delete_output(self, port):
        """
        Remove output port from node.

        Args:
            port (PortItem): port object.
        """
        self._delete_port(port, self._output_items.pop(port))

    def get_input_text_item(self, port_item):
        """
        Args:
            port_item (PortItem): port item.

        Returns:
            QGraphicsTextItem: graphic item used for the port text.
        """
        return self._input_items[port_item]

    def get_output_text_item(self, port_item):
        """
        Args:
            port_item (PortItem): port item.

        Returns:
            QGraphicsTextItem: graphic item used for the port text.
        """
        return self._output_items[port_item]

    @property
    def widgets(self):
        return self._widgets.copy()

    def add_widget(self, widget):
        self._widgets[widget.get_name()] = widget

    def get_widget(self, name):
        widget = self._widgets.get(name)
        if widget:
            return widget
        raise NodeWidgetError('node has no widget "{}"'.format(name))

    def has_widget(self, name):
        return name in self._widgets.keys()

    def from_dict(self, node_dict):
        super(NodeItem, self).from_dict(node_dict)
        widgets = node_dict.pop('widgets', {})
        for name, value in widgets.items():
            if self._widgets.get(name):
                self._widgets[name].value = value

class NodeItemVertical(NodeItem):
    """
    Vertical Node item.

    Args:
        name (str): name displayed on the node.
        parent (QtWidgets.QGraphicsItem): parent item.
    """

    def __init__(self, name='node', parent=None):
        super(NodeItemVertical, self).__init__(name, parent)
        font = QtGui.QFont()
        font.setPointSize(15)
        self.text_item.setFont(font)

    def paint(self, painter, option, widget):
        """
        Draws the node base not the ports.

        Args:
            painter (QtGui.QPainter): painter used for drawing the item.
            option (QtGui.QStyleOptionGraphicsItem):
                used to describe the parameters needed to draw.
            widget (QtWidgets.QWidget): not used.
        """
        self.auto_switch_mode()

        painter.save()
        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(QtCore.Qt.NoBrush)

        # base background.
        margin = 1.0
        rect = self.boundingRect()
        rect = QtCore.QRectF(rect.left() + margin,
                             rect.top() + margin,
                             rect.width() - (margin * 2),
                             rect.height() - (margin * 2))

        radius = 4.0
        painter.setBrush(QtGui.QColor(*self.color))
        painter.drawRoundedRect(rect, radius, radius)

        # light overlay on background when selected.
        if self.selected:
            painter.setBrush(QtGui.QColor(*NODE_SEL_COLOR))
            painter.drawRoundedRect(rect, radius, radius)

        # top & bottom edge background.
        padding = 2.0
        height = 10
        painter.setBrush(QtGui.QColor(0, 0, 0, 50))
        if self.selected:
            painter.setBrush(QtGui.QColor(*NODE_SEL_COLOR))
        for y in [rect.y() + padding, rect.height() - height - 1]:
            top_rect = QtCore.QRectF(rect.x() + padding, y,
                                     rect.width() - (padding * 2), height)
            painter.drawRoundedRect(top_rect, 3.0, 3.0)

        # node border
        border_width = 0.8
        border_color = QtGui.QColor(*self.border_color)
        if self.selected:
            border_width = 1.2
            border_color = QtGui.QColor(*NODE_SEL_BORDER_COLOR)
        border_rect = QtCore.QRectF(rect.left(), rect.top(),
                                    rect.width(), rect.height())

        pen = QtGui.QPen(border_color, border_width)
        pen.setCosmetic(self.viewer().get_zoom() < 0.0)
        path = QtGui.QPainterPath()
        path.addRoundedRect(border_rect, radius, radius)
        painter.setBrush(QtCore.Qt.NoBrush)
        painter.setPen(pen)
        painter.drawPath(path)

        painter.restore()

    def align_icon(self, h_offset=0.0, v_offset=0.0):
        """
        Align node icon to the right side of the node.

        Args:
            v_offset (float): vertical offset.
            h_offset (float): horizontal offset.
        """
        y = self._height / 2
        y -= self._icon_item.boundingRect().height()
        self._icon_item.setPos(self._width + h_offset, y + v_offset)

    def align_label(self, h_offset=0.0, v_offset=0.0):
        """
        Align node label to the right side of the node.

        Args:
            v_offset (float): vertical offset.
            h_offset (float): horizontal offset.
        """
        y = self._height / 2
        y -= self.text_item.boundingRect().height() / 2
        self.text_item.setPos(self._width + h_offset, y + v_offset)

    def align_ports(self, v_offset=0.0):
        """
        Align input, output ports in the node layout.
        """
        # adjust input position
        inputs = [p for p in self.inputs if p.isVisible()]
        if inputs:
            port_width = inputs[0].boundingRect().width()
            port_height = inputs[0].boundingRect().height()
            half_width = port_width/2
            delta = self._width / (len(inputs)+1)
            port_x = delta
            port_y = (port_height / 2) * -1
            for port in inputs:
                port.setPos(port_x - half_width, port_y)
                port_x += delta

        # adjust output position
        outputs = [p for p in self.outputs if p.isVisible()]
        if outputs:
            port_width = outputs[0].boundingRect().width()
            port_height = outputs[0].boundingRect().height()
            half_width = port_width / 2
            delta = self._width / (len(outputs)+1)
            port_x = delta
            port_y = self._height - (port_height / 2)
            for port in outputs:
                port.setPos(port_x-half_width, port_y)
                port_x += delta

    def draw_node(self):
        """
        Re-draw the node item in the scene.
        """
        # setup initial base size.
        self._set_base_size(add_w=0.0, add_h=0.0)
        # set text color when node is initialized.
        self._set_text_color(self.text_color)
        # set the tooltip
        self._tooltip_disable(self.disabled)

        # --- setup node layout ---
        # (do all the graphic item layout offsets here)

        # arrange label text
        self.align_label(h_offset=8)
        # arrange icon
        self.align_icon(h_offset=6, v_offset=-4)
        # arrange input and output ports.
        self.align_ports()
        # arrange node widgets
        self.align_widgets()

        self.update()

    def calc_size(self, add_w=0.0, add_h=0.0):
        """
        Calculate minimum node size.

        Args:
            add_w (float): additional width.
            add_h (float): additional height.
        """
        width = 0
        height = 0

        if self._widgets:
            wid_width = max([
                w.boundingRect().width() for w in self._widgets.values()
            ])
            width = max(width, wid_width)

        port_width = 0.0
        if self._input_items:
            port = list(self._input_items.keys())[0]
            port_width = port.boundingRect().width()

        if self._output_items:
            port = list(self._output_items.keys())[0]
            port_width = port.boundingRect().width()

        in_count = len([p for p in self.inputs if p.isVisible()])
        out_count = len([p for p in self.outputs if p.isVisible()])
        width = max(width, port_width * max(in_count, out_count))
        if self._widgets:
            wid_height = 0.0
            for w in self._widgets.values():
                wid_height += w.boundingRect().height()
            wid_height += wid_height / len(self._widgets.values())
            height = wid_height

        width += add_w
        height += add_h
        return width, height

    def add_input(self, name='input', multi_port=False, display_name=True,
                  locked=False, painter_func=None):
        """
        Adds a port qgraphics item into the node with the "port_type" set as
        IN_PORT

        Args:
            name (str): name for the port.
            multi_port (bool): allow multiple connections.
            display_name (bool): (not used).
            locked (bool): locked state.
            painter_func (function): custom paint function.

        Returns:
            PortItem: port qgraphics item.
        """
        return super(NodeItemVertical, self).add_input(
            name, multi_port, False, locked, painter_func)

    def add_output(self, name='output', multi_port=False, display_name=True,
                   locked=False, painter_func=None):
        """
        Adds a port qgraphics item into the node with the "port_type" set as
        OUT_PORT

        Args:
            name (str): name for the port.
            multi_port (bool): allow multiple connections.
            display_name (bool): (not used).
            locked (bool): locked state.
            painter_func (function): custom paint function.

        Returns:
            PortItem: port qgraphics item.
        """
        return super(NodeItemVertical, self).add_output(
            name, multi_port, False, locked, painter_func)


import base64
from reliabilly.tests import MagicMock, TestCase, logging, warnings, json, skipUnless
from reliabilly.components.factories.factory import Factory, Components
from reliabilly.components.services.queuing import SqsReceiver, QueueMessage
from reliabilly.settings import Settings, Constants

TEST = 'test'
DUMMY = 'dummy'
TEST_CONTENT = {'content': TEST}
TEST_MESSAGE = QueueMessage(**TEST_CONTENT)
ENCODED_TEST_MESSAGE = 'eyJtZXNzYWdlIjogeyJjb250ZW50IjogInRlc3QifSwgImlkIjogInRlc3QifQ=='
MOCK_RESPONSE = {Constants.MESSAGES_KEY: [{Constants.BODY_KEY: ENCODED_TEST_MESSAGE,
                                           Constants.RECEIPT_HANDLE_KEY: '123'}]}
DUMMY_MESSAGE = {'content': DUMMY}
ENCODED_DUMMY_MESSAGE = base64.b64encode(json.dumps(DUMMY_MESSAGE).encode())
LOCAL_SQS = 'http://localhost:9324'

class QueueingTests(TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)
        warnings.filterwarnings(Constants.IGNORE, category=ResourceWarning)

    def test_full_queue(self):
        mock = MagicMock()
        mock.create_queue.return_value = mock
        mock.get_queue_by_name.return_value = mock
        mock.receive_message.return_value = MOCK_RESPONSE
        client = Factory.create(Components.ServiceClients.SQS, queue_name=DUMMY, client=mock)
        self.assertIsNotNone(client)
        self.assertTrue(client.send_message(TEST_MESSAGE))
        mock.send_message.assert_called()
        message = client.receive_message()
        self.assertTrue(message)
        self.assertTrue(client.complete_message(message))
        mock.send_message.side_effect = Exception
        self.assertFalse(client.send_message(TEST_MESSAGE))
        mock.receive_message.side_effect = Exception
        self.assertFalse(client.receive_message())
        mock.delete_message.side_effect = Exception
        self.assertFalse(client.complete_message(message))

    def test_purge(self):
        mock = MagicMock()
        client = Factory.create(Components.ServiceClients.SQS, queue_name=DUMMY, client=mock)
        self.assertTrue(client.purge())
        mock.purge_queue.side_effect = Exception
        self.assertFalse(client.purge())

    def test_create_queue(self):
        mock = MagicMock()
        mock.get_queue_by_name.side_effect = Exception
        client = Factory.create(Components.ServiceClients.SQS, queue_name=DUMMY, client=mock)
        self.assertTrue(client.create_queue(DUMMY))
        mock.create_queue.side_effect = Exception
        self.assertFalse(client.create_queue(DUMMY))

class QueueReceiverTests(TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)
        warnings.filterwarnings(Constants.IGNORE, category=ResourceWarning)

    def test_setup(self):
        mock = MagicMock()
        queue = SqsReceiver(DUMMY, client=mock)
        queue.setup()
        self.assertIsNotNone(queue.client)
        mock.create_queue.assert_called()

    def test_run(self):
        mock = MagicMock()
        queue = SqsReceiver(DUMMY, client=mock)
        queue.exit = True
        self.assertTrue(queue.run(mock))
        self.assertTrue(queue.run())
        queue.container = mock
        queue.start()

class TestQueueMessage(TestCase):
    def test_message_encoding(self):
        message = TEST_MESSAGE
        self.assertIsNotNone(message.body)
        self.assertEqual(len(message.body), 100)  # the encoded length in bytes will always include the changing id

    def test_message_decoding(self):
        message = QueueMessage.create_from_encoded(ENCODED_TEST_MESSAGE)
        self.assertIsNotNone(TEST_MESSAGE.message_id, message.message_id)
        self.assertEqual(TEST_CONTENT, message.message)

    def test_message_construction(self):
        message = QueueMessage(test=2, test2='test', message_id='123')
        self.assertEqual(str(message), "{'test': 2, 'test2': 'test', 'message_id': '123'}")

@skipUnless(Settings.RUN_SKIPPED, Constants.RUN_SKIPPED_MSG)
class TestRealQueue(TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)
        warnings.filterwarnings(Constants.IGNORE, category=ResourceWarning)

    def test_queue_lost_message(self):
        client = Factory.create(Components.ServiceClients.SQS, queue_name='test', endpoint_url=LOCAL_SQS)
        message = QueueMessage()
        message.product = "site.reliability"
        message.account_name = "reliabilly_ps"
        message.portfolio = "Platform Services"
        message.threshold = "5"
        message.slo_type = "error_rate"
        message.component = "reliabillyecs_cluster"
        message.query = "SELECT sum(`provider.httpCodeTarget5XXCount.Sum`) from LoadBalancerSample where " \
                        "`label.serverclass` like 'lighthouse' and `provider.awsRegion` = 'us-west-2'"
        message.query_executor = "newrelic"
        message.id = "0000000-ae7a-4896-a957-9b736128eaf3"
        message.name = "lighthouse-error_rate-uat"
        client.send_message(message)
        received_message = client.receive_message()
        self.assertEqual(message.message_id, received_message.message_id)
        client.complete_message(received_message)

    def test_actual_sqs(self):
        client = Factory.create(Components.ServiceClients.SQS, queue_name='test')
        model = QueueMessage()
        model.message = 'This is a test ___'
        model.name = 'ODB'
        model.value = 420
        self.assertTrue(client.send_message(model))
        message = client.receive_message()
        self.assertEqual(message.name, model.name)
        client.complete_message(message)

    def test_actual_queue_message(self):
        client = Factory.create(Components.ServiceClients.SQS, queue_name='qm-queue')
        self.assertTrue(client.send_message(QueueMessage(**DUMMY_MESSAGE)))
        message = client.receive_message()
        self.assertIsNotNone(message)
        client.complete_message(message)


import os
import sys
sys.path.append(os.pardir)
from Notebooks.LinkDatabases.FacebookData import FacebookDataDatabase
import os
facebookDb = FacebookDataDatabase()
files = os.listdir("./images")

delete_paths = []

ids = list(map(lambda x: x.replace(".png","").replace(".jpg",""),files))
for x in ids:
    shareCount = facebookDb.getShareCount(x)
    if shareCount is None:
        print(x)
        delete_paths.append(os.path.join("./images",x))

print()
for x in ids:
    shareCount = facebookDb.getCommentCount(x)
    if shareCount is None:
        print(x)
        delete_paths.append(os.path.join("./images",x))

#should_delete = input("Should we delete these files?")
#if should_delete == 'y' or should_delete == 'yes':
#    for path in delete_paths:
#        os.remove(path)


#COLORS
import os

dark_blue = '#0f1925'
light_blue = '#3a558c'
dark_green  = '#162113'
light_green = '#676a45'
dark_coat = '#412521 '
light_coat = '#5d3232'

os.environ['frame_border_active_color'] =  '#222222cc' #IDK
os.environ['frame_border_active_color'] = '#ff5d3232'
os.environ['frame_border_normal_color'] = dark_blue # panel top
os.environ['frame_bg_normal_color'] = dark_blue #nieaktywne okno ramka
os.environ['frame_bg_active_color'] = '#3a558c' #aktywne okno ramka

os.environ['theme_active_color'] = light_coat # ramka okna?
os.environ['theme_title_color'] = '#ffffff' # text?
os.environ['theme_normal_color'] = dark_coat # #323232dd okno nieaktywne
os.environ['theme_urgent_color'] = '#7811A1dd'
os.environ['theme_normal_title_color'] = '#898989' # text?


"""
Use the saleae module to interface with the Saleae Logic analyzer to capture i2c traffic

Notes:
    - Make sure the Logic GUI has scripting enabled:
      options -> preferences -> developer -> Enable scripting socket server

"""
import time
import logging
from saleae import Saleae
import pandas as pd
import defs

log = logging.getLogger(__name__)

CAPTURE_OUTPUT_CSV = ".\\i2c_capture.csv"

def init_saleae():
    sal = Saleae()
    analyzers = sal.get_analyzers()
    log.info(f"Saleae analyzers: {analyzers}")
    return sal

def run(sal, capture_seconds):
    """

    Args:
        sal (Saleae): Saleae object
        capture_seconds:

    Returns:

    """

    sal.set_capture_seconds(capture_seconds)
    sal.capture_start_and_wait_until_finished()
    sal.export_analyzer(analyzer_index=1, wait_for_processing=True, save_path=CAPTURE_OUTPUT_CSV)
    time.sleep(0.1)  # Make sure export is finished so that CSV file contains latest capture

def process_capture(path=CAPTURE_OUTPUT_CSV):
    """Process capture of 1-byte i2c traffic and convert into one transmission per line

    Args:
        path: path to exported i2c analyzer (CSV)

    Returns:

    """

    df = pd.read_csv(path)
    log.info(f"Imported df:\n {df.head(5)}")

    # First byte (always write) is register value
    df['Reg'] = df['Data']

    # Second byte (shifted up one) is R/W data
    df['R/W'] = df['Read/Write'].shift(-1)
    df['Data'] = df['Data'].shift(-1)

    print(df.head(5))

    # Combine SMB write/read transactions into single rows
    i2c_df: pd.DataFrame = df[df['ACK/NAK'] != "NAK"].copy()
    i2c_df.drop(columns=["Read/Write"], inplace=True)
    print(i2c_df.head(5))

    log.info(f"Address dtypes: {i2c_df['Address'].dtypes}")
    i2c_df['IC'] = i2c_df['Address'].apply(int, base=16).map(defs.I2C_ADDR_MAP)

    i2c_df.to_csv("i2c_analyzed.csv")

def process_2byte_capture(path):
    """Process capture of 2-byte i2c traffic and convert into one transmission per line

    Args:
        path: path to exported i2c analyzer (CSV)

    Returns:

    """
    df = pd.read_csv(path)
    log.info(f"Imported df from {path}:")
    log.info(df.head(5))

    # Filter out non-responsive traffic
    df.dropna(subset=['Packet ID'], inplace=True)
    log.debug("Dropped empty packet IDs")
    log.debug(df.head(5))

    # todo - make sure data is aligned so assignments are correct even if captured in the middle of traffic
    # First 2 bytes (always writes) are register values
    df['Reg0'] = df['Data']
    df['Reg1'] = df['Data'].shift(-1)

    # 3rd byte is R/W data
    df['R/W'] = df['Read/Write'].shift(-2)
    df['Data'] = df['Data'].shift(-2)

    # Use Data NAK for alignment and filtration
    df['Data NAK'] = df['ACK/NAK'].shift(-2)

    log.debug("Reassigned reg/data column names")
    log.debug(df.head(5))

    # Filter down to rows containing properly aligned transactions
    i2c_df = df[df['Data NAK'] == 'NAK'].copy()

    # Combine SMB write/read transactions into single rows
    # i2c_df: pd.DataFrame = df[df['ACK/NAK'] != "NAK"].copy()
    # i2c_df.drop(columns=["Read/Write"], inplace=True)

    log.debug("Filtered down to aligned transactions")
    log.debug(i2c_df.head(5))

    log.info(f"Address dtypes: {i2c_df['Address'].dtypes}")
    i2c_df['IC'] = i2c_df['Address'].apply(int, base=16).map(defs.I2C_ADDR_MAP)

    return i2c_df
    # i2c_df.to_csv("i2c_analyzed.csv")

if __name__ == '__main__':
    import sys
    logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
    # s = init_saleae()
    # run(sal=s, capture_seconds=1)
    # process_capture()
    i2c_2byte_df = process_2byte_capture(
        path=r"C:\Users\Robert King\Google Drive\LiliPad\MCU\Firmware\p9221_full_multi_reg_reads.csv"
    )
    i2c_2byte_df.to_csv("./i2c_2byte.csv")


from __future__ import division
import re
import os
import glob
import numpy as np
import cPickle as pickle
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

FPS=25

def frames_to_time(num_frames):
    return num_frames/FPS

# Get the frames stats
annotations_dir = '/Users/zal/CMU/Fall2015/HCMMML/FinalProject/Repository/DataProcessing/VideoAnnotation/ActionAnnotations'
action_times_path = '/Users/zal/CMU/Fall2015/HCMMML/FinalProject/Repository/DataProcessing/Analysisaction_times.p'
action_durations_path = '/Users/zal/CMU/Fall2015/HCMMML/FinalProject/Repository/DataProcessing/Analysisaction_durations.p'

actions_times = []
for action_filename in glob.glob(os.path.join(annotations_dir, '*.csv')):
    action_name = os.path.splitext(os.path.basename(action_filename))[0]
    annotations = open(action_filename).readlines()
    times = []
    for annotation in annotations:
        fields = annotation.split('\t')
        times.append((int(fields[1]), int(fields[2]), int(fields[2])-int(fields[1]))) #start and end frame
    actions_times.append((action_name, times))

pickle.dump(actions_times, open(action_times_path,'w'))

actions_durations = []
for action_times in actions_times:
    action_name = action_times[0]
    action_dur = filter(lambda x: x!=0, zip(*action_times[1])[2])  #Remove all those non-captured clips
    actions_durations.append((action_name, action_dur))

pickle.dump(actions_durations, open(action_durations_path,'w'))

# Make Data object made up of 4 Box objects
action_names = zip(*actions_durations)[0]
data = zip(*actions_durations)[1]

action_names = action_names[0:1]+action_names[3:len(action_names)-1]
data = data[0:1]+data[3:len(data)-1]

fig, ax1 = plt.subplots(figsize=(10, 6))
fig.canvas.set_window_title('Action Durations')
plt.subplots_adjust(left=0.075, right=0.95, top=0.9, bottom=0.25)

bp = plt.boxplot(data, notch=0, sym='+', vert=1, whis=1.5)
plt.setp(bp['boxes'], color='black')
plt.setp(bp['whiskers'], color='black')
plt.setp(bp['fliers'], color='red', marker='+')

# Add a horizontal grid to the plot, but make it very light in color
# so we can use it for reading data values but not be distracting
ax1.yaxis.grid(True, linestyle='-', which='major', color='lightgrey',
               alpha=0.5)

# Hide these grid behind plot objects
ax1.set_axisbelow(True)
ax1.set_title('Action Duration Distribution')
ax1.set_xlabel('Action')
ax1.set_ylabel('Frames')

light_green_color = '#92CD00'
yellow_color = '#FFCC00'
blue_color = '#3333FF'
orange_color = '#FF6600'

boxColors = [light_green_color, orange_color]
numBoxes = len(action_names)
medians = list(range(numBoxes))
for i in range(numBoxes):
    box = bp['boxes'][i]
    boxX = []
    boxY = []
    for j in range(5):
        boxX.append(box.get_xdata()[j])
        boxY.append(box.get_ydata()[j])
    boxCoords = list(zip(boxX, boxY))
    # Alternate between Dark Khaki and Royal Blue
    k = i % 2
    boxPolygon = Polygon(boxCoords, facecolor=boxColors[k])
    ax1.add_patch(boxPolygon)
    # Now draw the median lines back over what we just filled in
    med = bp['medians'][i]
    medianX = []
    medianY = []
    for j in range(2):
        medianX.append(med.get_xdata()[j])
        medianY.append(med.get_ydata()[j])
        plt.plot(medianX, medianY, 'k')
        medians[i] = medianY[0]
    # Finally, overplot the sample averages, with horizontal alignment
    # in the center of each box
    plt.plot([np.average(med.get_xdata())], [np.average(data[i])],
             color='w', marker='*', markeredgecolor='k')

# Set the axes ranges and axes labels
ax1.set_xlim(0.5, numBoxes + 0.5)
top = 200
bottom = 0
ax1.set_ylim(bottom, top)
xtickNames = plt.setp(ax1, xticklabels=action_names)
plt.setp(xtickNames, rotation=45, fontsize=12)

pos = np.arange(numBoxes) + 1
upperLabels = [str(np.round(s, 2)) for s in medians]
weights = ['bold', 'semibold']
for tick, label in zip(range(numBoxes), ax1.get_xticklabels()):
    k = tick % 2
    ax1.text(pos[tick], top - (top*0.05), upperLabels[tick],
             horizontalalignment='center', size='x-small', weight=weights[k],
             color=boxColors[k])

# Finally, add a basic legend
plt.figtext(0.80, 0.015, '*', color='white', backgroundcolor='silver',
            weight='roman', size='medium')
plt.figtext(0.815, 0.013, ' Average Value', color='black', weight='roman',
            size='x-small')

plt.show()


import sys
sys.stdin = open("퀵정렬.txt")

def swap(x, i, j):
    x[i], x[j] = x[j], x[i]

def pivotFirst(x, lmark, rmark):
    pivot_val = x[lmark]
    pivot_idx = lmark
    while lmark <= rmark:
        while lmark <= rmark and x[lmark] <= pivot_val:
            lmark += 1
        while lmark <= rmark and x[rmark] >= pivot_val:
            rmark -= 1
        if lmark <= rmark:
            swap(x, lmark, rmark)
            lmark += 1
            rmark -= 1
    swap(x, pivot_idx, rmark)
    return rmark

def quickSort(x, pivotMethod=pivotFirst):
    def _qsort(x, first, last):
        if first < last:
            splitpoint = pivotMethod(x, first, last)
            _qsort(x, first, splitpoint-1)
            _qsort(x, splitpoint+1, last)
    _qsort(x, 0, len(x)-1)

T = int(input())
for tc in range(1, T+1):
    N = int(input())
    arr = list(map(int, input().split()))
    quickSort(arr)
    print('#{} {}'.format(tc, arr[N//2]))


## Blender Python Script utility functions
###### created: 2022-05-10
###### updated: 2022-11-01

from typing import List
from typing import Dict
from typing import Tuple
import os

import bpy
from bpy.types import Action
from bpy.types import Armature
from bpy.types import BlendDataActions
from bpy.types import Bone
from bpy.types import Image
from bpy.types import Material
from bpy.types import Mesh
from bpy.types import Node
from bpy.types import NodeLink
from bpy.types import NodeSocket
from bpy.types import NodeSocketFloat
from bpy.types import NodeTree
from bpy.types import Object
from bpy.types import Pose
from bpy.types import Scene
from bpy.types import Sequence
from bpy.types import ShaderNodeBsdfPrincipled
from bpy.types import ShaderNodeTexImage
from bpy.types import VertexGroup
import bmesh
from bmesh.types import BMesh
from bmesh.types import BMVert

## [PEP:8 JP](https://pep8-ja.readthedocs.io/ja/latest/#)
## [Google style Python docstring](https://qiita.com/11ohina017/items/118b3b42b612e527dc1d)

#######################################################################################################
## 選択した頂点を表示する関数
def show_verts(coordinate: bool = False) -> None:
    """Function to display the selected vertices.

    Args:
        coordinate (bool): whether to show a vertex coordinate.
    Returns:
        None
    """
    ob: Object = bpy.context.object
    if ob.mode == "EDIT":
        print("-----") # separation
        bm: BMesh = bmesh.from_edit_mesh(ob.data)
        for bmvert in bm.verts:
            bmvert: BMVert
            if bmvert.select:
                if coordinate:
                    print(bmvert.co) # vertex coordinate
                print(bmvert.index) # vertex index
    else:
        print("object is not in edit mode.")

#######################################################################################################
## Object の情報を表示する関数
def show_object_info() -> None:
    for ob in bpy.data.objects:
        ob: Object
        print(f"{ob.name} type: {ob.type}")

#######################################################################################################
## Object のリストを取得する関数
def get_objects(type: str = "ALL") -> List[Object]:
    if type == "ALL":
        return bpy.data.objects
    else:
        return [ob for ob in bpy.data.objects if ob.type == type]

# MESH
# ARMATURE
# CAMERA
# LIGHT

#######################################################################################################
## Material の情報を表示する関数
def show_material_info() -> None:
    for material in bpy.data.materials:
        material: Material
        texture_node: ShaderNodeTexImage = bpy.data.materials[material.name].node_tree.nodes.get("Image Texture")
        if texture_node != None:
            print(f"{material.name} -- texture filepath: {texture_node.image.filepath}")
        else:
            print(f"{material.name} -- not image texture")

#######################################################################################################
## Material のリストを取得する関数
def get_materials() -> List[Material]:
    return bpy.data.materials

#######################################################################################################
## Armature の情報を表示する関数
def show_armature_info() -> None:
    for armature in bpy.data.armatures:
        armature: Armature
        print(f"{armature.name}")
        bone_list: List[Bone] = armature.bones
        for bone in bone_list:
            bone: Bone
            print(f"    {bone.name}")

#######################################################################################################
## Armature のリストを取得する関数
def get_armatures() -> List[Armature]:
    return bpy.data.armatures

#######################################################################################################
## Action のリストを取得する関数
def get_actions() -> BlendDataActions:
    return bpy.data.actions

# /////////////////////////////////////////////////////////////////////////////////////////////////////
def set_object_mode() -> None:
    """sets the mode to OBJECT.

    Parameters
    ----------
        None

    Returns
    -------
        None
    """
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

# /////////////////////////////////////////////////////////////////////////////////////////////////////
def init(object_name: str) -> None:
    """initializes the state.

    Parameters
    ----------
    object_name: string
        the name of the object to edit.

    Returns
    -------
        None
    """
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    # reset select
    bpy.ops.object.mode_set(mode="EDIT")
    bpy.ops.mesh.select_mode(type="VERT")
    bpy.ops.mesh.select_all(action="DESELECT")
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Object にウエイトを設定する関数
def set_weight(object_name: str, vertex_group_name: str, verts: List[int], value: float) -> None:
    # init
    init(object_name=object_name)
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    # select the vertex group(bone)
    vertex_group: VertexGroup = ob.vertex_groups.get(vertex_group_name)
    ob.vertex_groups.active_index = vertex_group.index
    # select left target vertices
    bpy.ops.object.mode_set(mode="OBJECT")
    mesh: Mesh = ob.data
    for vert in verts:
        #ob.data.vertices[vert].select = True
        mesh.vertices[vert].select = True
    # set weight to vertices
    bpy.ops.object.mode_set(mode="EDIT")
    bpy.context.scene.tool_settings.vertex_group_weight = value
    bpy.ops.object.vertex_group_assign()
    bpy.ops.mesh.select_all(action="DESELECT")
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Object にクリースを設定する関数
def set_crease(object_name: str, verts: List[int], value: float) -> None:
    # init
    init(object_name=object_name)
    # set crease
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    bpy.ops.object.mode_set(mode="EDIT", toggle=False)
    bpy.ops.mesh.select_mode(type="VERT")
    bpy.ops.mesh.select_all(action="DESELECT")
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
    mesh: Mesh = ob.data
    for vert in verts:
        #ob.data.vertices[vert].select = True
        mesh.vertices[vert].select = True
    bpy.ops.object.mode_set(mode="EDIT", toggle=False)
    bpy.ops.transform.edge_crease(value=value)
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

# #######################################################################################################
# ## Object に freestyle_face を設定する関数
# #### FIXME: ※未完成: バグあり
# def set_freestyle_face(object_name: str) -> None:
#     """Function to set freestyle_face.

#     TODO: specifying edges.

#     Args:
#         object (string): the name of the object to edit.
#     Returns:
#         None
#     """
#     # init
#     init(object_name=object_name)
#     # set freestyle face
#     ob: Object = bpy.data.objects[object_name]
#     bpy.context.view_layer.objects.active = ob
#     bpy.ops.object.mode_set(mode="EDIT")
#     bpy.ops.mesh.select_all(action="SELECT")
#     bpy.ops.mesh.select_mode(type="FACE")
#     bpy.ops.mesh.mark_freestyle_face(clear=False)
#     # set OBJECT mode
#     bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Object に freestyle_face を設定する関数
#### FIXME: ※未完成: バグあり
def set_freestyle_face(object_name: str, verts: List[int] = []) -> None:
    # init
    init(object_name=object_name)
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    # select verts
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
    # select all verts
    if len(verts) == 0:
        bpy.ops.object.mode_set(mode="EDIT", toggle=False)
        bpy.ops.mesh.select_all(action="SELECT")
    # select verts given
    else:
        mesh: Mesh = ob.data
        for vert in verts:
            mesh.vertices[vert].select = True
    # set EDIT mode and select FACE mode
    bpy.ops.object.mode_set(mode="EDIT", toggle=False)
    bpy.ops.mesh.select_mode(type="FACE")
    # mark freestyle_face
    bpy.ops.mesh.mark_freestyle_face(clear=False)
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Object の局面を適用する関数
def apply_subsurf(object_name: str) -> None:
    # apply subsurf
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    bpy.ops.object.modifier_apply(apply_as="DATA", modifier="subsurf")

#######################################################################################################
## Object の重複している頂点を削除する関数
def remove_double_vertices(object_name: str) -> None:
    # init
    init(object_name=object_name)
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    # set EDIT mode
    bpy.ops.object.mode_set(mode="EDIT")
    # select all mesh
    bpy.ops.mesh.select_mode(type="VERT")
    bpy.ops.mesh.select_all(action="SELECT")
    # remove double vertices
    bpy.ops.mesh.remove_doubles(threshold=0.001, use_unselected=True)
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Object の頂点を指定の頂点に移動する関数
def move_vertex(object_name: str, data: Dict[int, Tuple[float, float, float]]) -> None:
    # init
    init(object_name=object_name)
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    # set EDIT mode
    bpy.ops.object.mode_set(mode="EDIT", toggle=False)
    # get the mesh
    bm: BMesh = bmesh.from_edit_mesh(mesh=ob.data)
    bm.verts.ensure_lookup_table()
    # move a vert to new vert
    for key, value in data.items():
        #print(key, value)
        bmvert: BMVert = bm.verts[key]
        # Magic UV on
        bpy.context.scene.muv_texture_lock_lock = True
        bmvert.co = value
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Material のテクスチャ画像を置き換える関数
def replace_textures() -> None:
    # get path of this file
    path: str = bpy.data.filepath
    dir: str = os.path.dirname(path)
    # replace textures
    for material in bpy.data.materials:
        material: Material
        name: str = material.name
        node_tree: NodeTree = material.node_tree
        if node_tree != None:
            # Principled BSDF
            bsdf_node: ShaderNodeBsdfPrincipled = node_tree.nodes.get("Principled BSDF")
            if bsdf_node == None:
                continue
            # Alpha
            alpha_node_socket: NodeSocket = bsdf_node.inputs[18]
            for link in alpha_node_socket.links:
                link: NodeLink
                node_tree.links.remove(link=link)
            # Normal
            normal_node_socket: NodeSocket = bsdf_node.inputs[19]
            for link in normal_node_socket.links:
                link: NodeLink
                node_tree.links.remove(link=link)
            # Image Texture
            texture_node: ShaderNodeTexImage = node_tree.nodes.get("Image Texture")
            if texture_node == None:
                continue
            prefix = name.split('.')[0]
            image: Image = bpy.data.images.load(filepath=f"{dir}\\textures\\{prefix}.png")
            texture_node.image = image
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Material の現状で使用しないノードを削除する関数
def delete_unused_nodes() -> None:
    # delete unused nodes
    for material in bpy.data.materials:
        material: Material
        node_tree: NodeTree = material.node_tree
        if node_tree != None:
            for node in node_tree.nodes:
                node: Node
                # delete "Normal Map" node
                if "Normal Map" in node.name:
                    node_tree.nodes.remove(node=node)
                # delete "Image Texture.001" node
                elif "Image Texture.001" in node.name:
                    node_tree.nodes.remove(node=node)
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## 使用しないテクスチャを削除する関数
def delete_unused_images() -> None:
    # delete unused images
    for image in bpy.data.images:
        image: Image
        #if not image.users:
        if "Diffuse Texture" in image.name:
            bpy.data.images.remove(image=image)
    # set OBJECT mode
    bpy.ops.object.mode_set(mode="OBJECT", toggle=False)

#######################################################################################################
## Material のシェーダーを調整する関数
def flatten_shader_or_not(value: float) -> None:
    # flatten materials
    for material in bpy.data.materials:
        material: Material
        node_tree: NodeTree = material.node_tree
        if node_tree != None:
            # get Principled BSDF
            bsdf_node: ShaderNodeBsdfPrincipled = node_tree.nodes.get("Principled BSDF")
            if bsdf_node == None:
                continue
            # set Metallic param
            metallic_node_socket: NodeSocketFloat = bsdf_node.inputs[4]
            metallic_node_socket.default_value = value
            # set Specular param
            specular_node_socket: NodeSocketFloat = bsdf_node.inputs[5]
            specular_node_socket.default_value = value

#######################################################################################################
## アクティブ Object の Bone リストを取得する関数
### MEMO: アクティブの必要がある？
def get_active_object_bones(object_name: str) -> List[Bone]:
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    active_object: Object = bpy.context.active_object # 必要？
    # get bones of the object
    armature: Armature = active_object.data
    bones: List[Bone] = armature.bones
    return bones

#######################################################################################################
## アクティブ Object の Pose を取得する関数
### MEMO: アクティブの必要がある？
def get_active_object_pose(object_name: str) -> Pose:
    # select the object
    ob: Object = bpy.data.objects[object_name]
    bpy.context.view_layer.objects.active = ob
    active_object: Object = bpy.context.active_object # 必要？
    # get pose of the object
    pose: Pose = active_object.pose
    return pose

#######################################################################################################
## 空の Object を削除する関数 ※再帰処理はしていない
def delete_empty_objects() -> None:
    for ob in bpy.data.objects:
        ob: Object = ob
        if ob.type == "EMPTY":
            bpy.data.objects.remove(object=ob)
    # do twice
    for ob in bpy.data.objects:
        ob: Object = ob
        if ob.type == "EMPTY":
            bpy.data.objects.remove(object=ob)

#######################################################################################################
## 孤立したデータを削除する関数
def delete_orphan_data() -> None:
    _delete_orphan_objects()
    _delete_orphan_armatures()
    _delete_orphan_meshes()
    _delete_orphan_materials()
    _delete_orphan_images()
    _delete_orphan_actions()
    # do twice
    _delete_orphan_objects()
    _delete_orphan_armatures()
    _delete_orphan_meshes()
    _delete_orphan_materials()
    _delete_orphan_images()
    _delete_orphan_actions()

def _delete_orphan_objects() -> None:
    orphan_list = [ob for ob in bpy.data.objects if ob.users == 0]
    for orphan in orphan_list:
        bpy.data.objects.remove(object=orphan)

def _delete_orphan_armatures() -> None:
    orphan_list = [armature for armature in bpy.data.armatures if armature.users == 0]
    for orphan in orphan_list:
        bpy.data.armatures.remove(armature=orphan)

def _delete_orphan_meshes() -> None:
    orphan_list = [mesh for mesh in bpy.data.meshes if mesh.users == 0]
    for orphan in orphan_list:
        bpy.data.meshes.remove(mesh=orphan)

def _delete_orphan_materials() -> None:
    orphan_list = [material for material in bpy.data.materials if material.users == 0]
    for orphan in orphan_list:
        bpy.data.materials.remove(material=orphan)

def _delete_orphan_images() -> None:
    orphan_list = [image for image in bpy.data.images if image.users == 0]
    for orphan in orphan_list:
        bpy.data.images.remove(image=orphan)

def _delete_orphan_actions() -> None:
    orphan_list = [action for action in bpy.data.actions if action.users == 0]
    for orphan in orphan_list:
        bpy.data.actions.remove(action=orphan)

#######################################################################################################
## FPS を設定する関数
def set_scene_render_fps(fps: int = 24, frame_step: int = 3) -> None:
    bpy.context.scene.render.fps = fps
    bpy.context.scene.frame_step = frame_step

#######################################################################################################
## タイムラインの終点を設定する関数
def set_scene_frame_end(frame_end: int) -> None:
     bpy.context.scene.frame_end = frame_end
     bpy.context.scene.frame_preview_end = frame_end

#######################################################################################################
## タイムラインのマーカーを全て削除する関数
def delete_scene_timeline_markers() -> None:
    bpy.context.scene.timeline_markers.clear()

#######################################################################################################
## タイムラインにマーカーを設定する関数
def set_scene_timeline_marker_to(name: str, frame: int) -> None:
    bpy.context.scene.timeline_markers.new(name=name, frame=frame)

#######################################################################################################
## シーケンサーに画像を設定する関数
def add_sequencer_image(name: str, file_path: str, channel: int, frame_start: int, frame_end: int, blend_type: str,
    fade_duration_seconds: float = 0.0, fade_in_duration_seconds: float = 0.0, fade_out_duration_seconds: float = 0.0) -> None: #, num: int = 0
    #if num is not 0: # シフト用に番号付加
    #    name = f"{name}_{int(num)}"
    sequence: Sequence = bpy.context.scene.sequence_editor.sequences.new_image(name=name, filepath=file_path, channel=channel, frame_start=frame_start)
    sequence.frame_final_start = frame_start
    sequence.frame_final_end = frame_end # 実際には下の処理で +1 される
    sequence.frame_final_duration = frame_end - frame_start + 1 # +1 で次の開始に重なる
    sequence.blend_type = blend_type
    if fade_duration_seconds != 0.0 or fade_in_duration_seconds != 0.0 or fade_out_duration_seconds != 0.0:
        bpy.context.scene.sequence_editor.active_strip = sequence
        if fade_duration_seconds != 0.0:
            bpy.ops.sequencer.fades_add(duration_seconds=fade_duration_seconds, type="IN_OUT")
        if fade_in_duration_seconds != 0.0:
            bpy.ops.sequencer.fades_add(duration_seconds=fade_in_duration_seconds, type="IN")
        if fade_out_duration_seconds != 0.0:
            bpy.ops.sequencer.fades_add(duration_seconds=fade_out_duration_seconds, type="OUT")
    sequence.select = False # 重要

#######################################################################################################
## シーケンサーにシーンを設定する関数
def add_sequencer_scene(name: str, scene_name: str, channel: int, frame_start: int, frame_end: int, blend_type: str) -> None:
    bpy.context.scene.render.line_thickness = 2.5
    bpy.context.scene.render.film_transparent = True
    bpy.ops.scene.new(type="FULL_COPY")
    scene: Scene = bpy.data.scenes[scene_name]
    sequence: Sequence = bpy.context.scene.sequence_editor.sequences.new_scene(name=name, scene=scene, channel=channel, frame_start=frame_start)
    sequence.frame_final_start = frame_start
    sequence.frame_final_end = frame_end # 実際には下の処理で +1 される
    sequence.frame_final_duration = frame_end - frame_start + 1 # +1 で次の開始に重なる
    sequence.blend_type = blend_type

#######################################################################################################
## ファイルの存在を確認する関数
def exists_file(file_path: str) -> bool:
    is_file: bool = os.path.isfile(path=file_path)
    return is_file

#######################################################################################################
## この .blend ファイルのディレクトリを取得する関数
def get_current_dir() -> str:
    current_path: str = bpy.data.filepath
    current_dir: str = os.path.dirname(current_path)
    return current_dir

#######################################################################################################
## 現在のフレーム値を取得する関数
def get_current_frame() -> int:
    frame: int = bpy.context.scene.frame_current
    return frame

#######################################################################################################
## 引数値のフレームを選択する関数
def set_current_frame(frame: int) -> int:
    bpy.context.scene.frame_set(frame)

#######################################################################################################
## デフォルトのカメラを取得する関数
def get_default_camera() -> Object:
    camera: Object = bpy.data.objects["Camera"]
    return camera

#######################################################################################################
## start_frame からカットの end_frame を計算して返す関数
# def get_cut_end_frame(start_frame: int, cut_duration_and_start_frame_list: List[Tuple[int, int]]) -> int:
#     fps: float = 24.0 # FIXME:
#     for idx, value in enumerate(cut_duration_and_start_frame_list):
#         cut_duration: int = value[0]
#         cut_start_frame: int = value[1]
#         if start_frame == cut_start_frame:
#             return start_frame + ((cut_duration * fps) - 1)

# #######################################################################################################
# ## 補完曲線を一定に設定する関数
# def _set_curve_constant(action_name: str) -> None:
#     bpy.data.scenes["Scene"].frame_set(frame)
#     action: Action = bpy.data.actions["CameraAction"]
#     fcurves: ActionFCurves = action.fcurves
#     for fcurve in fcurves:
#         fcurve: FCurve
#         print(fcurve.data_path + " channel " + str(fcurve.array_index))
#         for keyframe in fcurve.keyframe_points:
#             keyframe: Keyframe
#             keyframe.interpolation = "CONSTANT"


#参数：
'''
    criterion:                  可以使用"gini"或者"entropy"，前者代表基尼系数，后者代表信息增益。
    splitter:                   特征划分点选择标准， best 或者 random
    max_features:               划分时考虑的最大特征数，
    max_depth:                  决策树最大深度，默认可以不输入
    min_samples_split:          内部节点再划分所需最小样本数
    min_weight_faction_leaf:    叶子节点最小的样本权重和
    max_leaf_nodes:             最大叶子节点数
    class_weight:               类别权重值
    min_impurity_split:         节点划分最小不纯度
    presort:                    数据是否预排序
'''
from itertools import product

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier

# 仍然使用自带的iris数据
iris = datasets.load_iris()
X = iris.data[:, [0, 2]]                                        #数据集
y = iris.target                                                 #标签

# 训练模型，限制树的最大深度4
clf = DecisionTreeClassifier(criterion='entropy',max_depth=4)
#拟合模型
clf.fit(X, y)

# 画图
#生成测试数据
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))
#利用模型对测试数据进行分类预测
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, alpha=0.4)                                #绘制等高线
plt.scatter(X[:, 0], X[:, 1], c=y, alpha=0.8)
plt.show()


import itertools

def getInput(name):
    with open(name+'.txt', 'r') as file:
        return file.read().strip().split('\n')

def sumSizes(lines):
    sumSizes = [] # part 1
    accumulateSizes = [] # part 2
    for line in lines:
        command = line.split(' ')

        if command[0] == '$':
            if command[1] == 'cd':
                if command[2] == '..':
                    sumSizes.append(accumulateSizes.pop())
                    accumulateSizes[-1] += sumSizes[-1]
                else: # $cd newDir
                    accumulateSizes.append(0)
            else: pass # $ ls
        elif command[0] != 'dir': # output of LS for files
            accumulateSizes[-1] += int(command[0])

    return sumSizes + list(itertools.accumulate(accumulateSizes[::-1]))

def part1(lines):
    lowestDirSizes = filter(lambda x: x <= 100_000, sumSizes(lines))
    return sum(lowestDirSizes)

def part2(lines):
    sumDirSizes = sumSizes(lines)
    available = 70_000_000 - sumDirSizes[-1]
    return min(filter(lambda x: x + available >= 30_000_000, sumDirSizes))

input = getInput('sample')
# input = getInput('input')

print('>>> part 1:', part1(input)) # sample: 95437
print('>>> part 2:', part2(input)) # sample: 24933642


import numpy as np
import os

from scipy import signal

from astropy.stats import sigma_clipped_stats
from astropy.table import Table

from photutils import DAOStarFinder, IRAFStarFinder, find_peaks

from specklepy.exceptions import SpecklepyTypeError, SpecklepyValueError
from specklepy.io.fits import get_data
from specklepy.logging import logger
from specklepy.plotting.plot import StarFinderPlot
from specklepy.utils import save_eval
from specklepy.utils.array import peak_index
from specklepy.utils.box import Box
from specklepy.utils.moment import moment_2d
from specklepy.utils.point import Point

def extract_sources(image, noise_threshold, fwhm, star_finder='DAO', image_var=None, background_subtraction=True,
                    show=False, select=False, matching_threshold=30, collapse=False, write_to=None, cast_dtype=None,
                    debug=False):
    """Extract sources from an image with a StarFinder routine.

    Long description...

    Args:
        image (np.ndarray or str):
            Image array or the name of a file containing the image array.
        noise_threshold (float):
            Multiple of the uncertainty/ standard deviation of the image.
        fwhm (float):
            Expected full width at half maximum (FWHM) of the sources in units of pixels.
        star_finder (str, optional):
            Choose whether the 'DAO' or 'IRAF' StarFinder implementations from photutils shall be used. Default is
            'DAO'.
        image_var (float or str):
            Variance of the image used for the StarFinder threshold (=noise_threshold * sqrt(image_var)). If not
            provided, the code extracts this value from sigma clipped stats. If provided as str-type, the code tries to
            use this as a key to the FITS file HDU list.
        background_subtraction (bool, optional):
            Let the StarFinder consider the background subtraction. Set False for ignoring background flux. Default is
            `True`.
        show (bool, optional):
            Create a plot of the identified sources on the image.
        select (bool or dict, optional):
            Create a plot of the identified sources on the image, with the option of selecting apertures for future
            purposes. These are selected by clicking on the image.
        matching_threshold (int, optional):
            Radial threshold for accepting a source as matched or, when manually selecting peaks, the radius of the
            search box.
        collapse (bool, optional):
            Collapse the a data cube along the third axis prior to analysis.
        write_to (str, optional):
            If provided as a str, the list of identified sources is saved to this file.
        cast_dtype (str, optional):
            Str-representation of a data type to cast the image to, prior to the extraction process.
        debug (bool, optional):
            Show debugging information. Default is `False`.

    Returns:
        sources (astropy.table.Table):
            Table of identified sources, `None` if no sources are detected.
        selected (list, optional):
            List of selected sources, returned only if `select!=False`.
    """

    # Set logger level
    if debug:
        logger.setLevel('DEBUG')

    # Initialize the extractor
    extractor = SourceExtractor(algorithm=star_finder, fwhm=fwhm, sigma=noise_threshold)
    extractor.initialize_image(image, extension=None, var=image_var, dtype=cast_dtype, collapse=collapse)
    extractor.initialize_star_finder()

    # Reset parameters if requested
    if not background_subtraction:
        extractor.image.sky_bkg = 0.0
        logger.debug(f"Resetting sky background to {extractor.image.sky_bkg}")

    # Find sources
    sources = extractor.find_sources(uncertainties=True)

    # Save sources table to file, if requested
    if write_to is not None:
        extractor.write_to(write_to)

    if show:
        extractor.show()

    if select:
        # Interpret select type
        message = None
        save_to = None
        if isinstance(select, dict):
            message = select.get('message', 'Please select sources!')
            save_to = select.get('save_to', None)
        elif isinstance(select, str):
            message = 'Please select sources!'
            save_to = select

        # Select sources
        selected = extractor.select(save_to=save_to, message=message, radius=matching_threshold)
        logger.info(f"Selected {len(selected)} sources")
        logger.debug(f"\n{selected}")
        return sources, selected

    return sources

class SourceExtractor(object):

    def __init__(self, fwhm, algorithm='DAO', sigma=5.0):

        # Store input
        self.fwhm = fwhm
        self.sigma = sigma
        self.algorithm = algorithm

        # Initialize attributes
        self.image = None
        self.sources = None

    @property
    def threshold(self):
        return self.sigma * np.mean(self.image.stddev)

    def initialize_image(self, source, extension=None, var=None, dtype=None, collapse=False):
        if isinstance(source, str):
            self.image = StarFinderImage.from_file(source, extension=extension, var=var, dtype=save_eval(dtype))
        else:
            self.image = StarFinderImage(source, var=var)

        # Collapse data cube
        if collapse:
            self.image.collapse_image()

    def initialize_star_finder(self):

        # Build parameter dictionary
        params = {'fwhm': self.fwhm, 'threshold': self.threshold}  # , 'sky': self.image.sky_bkg}

        # Type and value check on algorithm
        if not isinstance(self.algorithm, str):
            raise SpecklepyTypeError('extract_sources', argname='starfinder', argtype=type(self.algorithm),
                                     expected='str')
        if 'dao' in self.algorithm.lower():
            star_finder = DAOStarFinder(**params)
        elif 'iraf' in self.algorithm.lower():
            star_finder = IRAFStarFinder(**params)
        elif 'peak' in self.algorithm.lower():
            star_finder = PeakFinder(**params)
        elif 'manu' in self.algorithm.lower():
            star_finder = ManualFinder(**params)
        else:
            raise SpecklepyValueError('extract_sources', argname='star_finder', argvalue=self.algorithm,
                                      expected="'DAO', 'IRAF', or 'PeakFinder'")
        logger.debug(f"StarFinder algorithm is initialized as: {star_finder}")

        return star_finder

    def find_sources(self, uncertainties=False):

        # Initialize StarFinder algorithm
        star_finder = self.initialize_star_finder()

        # Find stars
        logger.info("Extracting sources...")
        logger.debug(f"Extraction parameters:"
                     f"\n\tFWHM = {self.fwhm}"
                     f"\n\tThreshold = {self.threshold}"
                     f"\n\tSky = {self.image.sky_bkg}")
        sources = star_finder(np.ma.masked_less(self.image.data - self.image.sky_bkg, 0).filled(0))

        # Reformatting sources table
        sources.sort('flux', reverse=True)
        sources = self.rename_columns(sources)
        sources.keep_columns(['x', 'y', 'flux'])

        # Re-measure positions and flux to obtain uncertainties
        if uncertainties:
            sources = self.estimate_uncertainties(sources=sources)

        # Add terminal output
        logger.info(f"Extracted {len(sources)} sources")
        logger.debug(f"\n{sources}")

        # Store source table
        self.sources = sources
        return sources

    @staticmethod
    def rename_columns(table):
        aliases = {'xcentroid': 'x', 'ycentroid': 'y', 'x_peak': 'x', 'y_peak': 'y', 'peak_value': 'flux'}
        for key, alias in aliases.items():
            if key in table.colnames:
                table.rename_column(key, alias)
        return table

    def estimate_uncertainties(self, sources):
        new_sources = Table(names=['x', 'dx', 'y', 'dy', 'flux', 'dflux'])
        radius = round(self.fwhm / 2.35 * 1.5)  # 1.5 times the standard deviation
        logger.info(f"Measuring uncertainties over {(radius * 2 + 1)}x{radius * 2 + 1} pixels...")

        # Iterate through sources
        for source in sources:
            pos = int(round(source['y'])), int(round(source['x']))
            box = Box.centered_at(pos[1], pos[0], radius=radius)
            box.crop_to_shape(shape=self.image.data.shape)
            aperture = box(self.image.data)
            try:
                var = box(np.square(self.image.stddev))
            except TypeError:
                var = None
            except IndexError:
                var = np.full(aperture.shape, np.square(self.image.stddev))

            # Compute moments and uncertainties, and add to new table
            try:
                f, df, x, dx, y, dy = moment_2d(aperture, var=var)
                new_sources.add_row([x + box.x_min, dx, y + box.y_min, dy, f, df])
            except ValueError:
                logger.warning(f"Unsuccessful to measure uncertainties for source in box {box}")
                new_sources.add_row([source['x'], 0, source['y'], 0, source['flux'], 0])

        return new_sources

    def show(self):
        plot = StarFinderPlot(image_data=self.image.data)
        if self.sources is not None:
            positions = np.transpose((self.sources['y'], self.sources['x']))
            plot.add_apertures(positions=positions, radius=self.fwhm / 2)
        plot.show()

    def select(self, save_to=None, message=None, radius=None):
        if self.sources is None:
            logger.warning("SourceExtractor does not store identified sources yet! Finding sources now...")
            self.find_sources()

        # Create plot
        plot = StarFinderPlot(image_data=self.image.data)
        positions = np.transpose((self.sources['y'], self.sources['x']))
        plot.add_apertures(positions=positions, radius=self.fwhm / 2)

        # Prompt message
        if message is not None:
            logger.info(message)

        # Graphically select apertures
        positions = plot.select_apertures(marker_size=100)
        selected = self.cross_match(positions, radius_threshold=radius)

        # Save results
        if save_to is not None and len(selected) > 0:
            selected.write(save_to, format='ascii.fixed_width', overwrite=True)

        return selected

    def cross_match(self, guesses, radius_threshold=None):
        indexes = []
        for guess in guesses:
            distances = self.compute_distances(guess)
            try:
                if np.min(distances) > radius_threshold:
                    # Reject source to far away from known sources
                    continue
            except TypeError:
                pass
            indexes.append(np.argmin(distances))
        return self.sources[indexes]

    def write_to(self, filename):
        """Save source table to a file"""
        if filename == 'default':
            filename = self.default_file_name(self.image.filename)
        if self.sources is not None:
            logger.info(f"Writing list of sources to file {filename!r}")
            self.sources.write(filename, format='ascii.fixed_width', overwrite=True)

    @staticmethod
    def default_file_name(filename):
        return 'sources_' + os.path.basename(filename).replace('.fits', '.dat')

    def compute_distances(self, position):
        dx = position[1] - self.sources['x'].data
        dy = position[0] - self.sources['y'].data
        return np.sqrt(np.add(np.square(dx), np.square(dy)))

    def cross_match_table(self, table):

        # Initialize array of summed distances
        minimum_distances_total = np.empty((len(self.sources)))

        # Reference pattern: Stellar positions relative to first star
        table_dx = table['x'] - table['x'][0]
        table_dy = table['y'] - table['y'][0]

        # Iterate through sources to identify reference star from `table`
        for j in range(len(self.sources)):
            # Stellar positions relative to source `j`
            sources_dx = self.sources['x'] - self.sources['x'][j]
            sources_dy = self.sources['y'] - self.sources['y'][j]

            # Compute distances
            for i, (xi, yi) in enumerate(zip(table_dx, table_dy)):
                ri = np.sqrt((xi - sources_dx) ** 2 + (yi - sources_dy) ** 2)
                j_min = np.argmin(ri)
                minimum_distances_total[j] += ri[j_min]

        # Estimate distance-minimizing counter part to reference star
        best_match = np.argmin(minimum_distances_total)

        # Estimate shift
        shift = table[0]['y'] - self.sources[best_match]['y'], table[0]['x'] - self.sources[best_match]['x']

        return shift

class StarFinderImage(object):

    def __init__(self, image, filename=None, var=None):
        self.data = image
        self.filename = filename
        self._stddev = np.sqrt(var) if var is not None else None
        self._sky_bkg = None

    @property
    def stddev(self):
        if self._stddev is None:
            _, self._stddev = self.sigma_clipped_statistics()
        return self._stddev

    @stddev.setter
    def stddev(self, value):
        self._stddev = value

    @property
    def var(self):
        return np.square(self.stddev)

    @property
    def sky_bkg(self):
        if self._sky_bkg is None:
            self._sky_bkg, _ = self.sigma_clipped_statistics()
        return self._sky_bkg

    @sky_bkg.setter
    def sky_bkg(self, value):
        self._sky_bkg = value

    @classmethod
    def from_file(cls, filename, extension=None, var=None, dtype=None):
        logger.info(f"Read FITS image from file {filename!r} [{str(extension)}]")
        image = get_data(filename, extension=extension, dtype=dtype, squeeze=True)
        logger.debug(f"Data type of file input is {image.dtype}")
        if isinstance(var, str):
            var = get_data(filename, extension=var, dtype=dtype, squeeze=True)
        return cls(image=image, filename=filename, var=var)

    def collapse_image(self):
        self.data = np.sum(self.data, axis=0)

    def sigma_clipped_statistics(self, sigma=3.0):
        mean, median, std = sigma_clipped_stats(data=self.data, sigma=sigma)
        logger.info(f"Noise statistics for {self.filename!r}:"
                    f"\n\tMean = {mean:.3}\n\tMedian = {median:.3}\n\tStdDev = {std:.3}")
        return mean, std

class PeakFinder(object):

    def __init__(self, fwhm, threshold):
        self.fwhm = fwhm
        self.threshold = threshold

    def __call__(self, image, *args, **kwargs):
        # peaks = find_peaks_2d(array=image, height=self.threshold, width=self.fwhm)#, threshold=5, distance=1)
        # sources = Table(names=['xcentroid', 'ycentroid', 'flux'])
        # for peak in peaks:
        #     sources.add_row([peak[0], peak[1], image[peak[1], peak[0]]])
        sources = find_peaks(data=image, threshold=self.threshold, box_size=1.5*self.fwhm)
        sources.rename_column('x_peak', 'xcentroid')
        sources.rename_column('y_peak', 'ycentroid')
        sources.rename_column('peak_value', 'flux')
        return sources

    def __repr__(self):
        return f"PeakFinder(fwhm: {self.fwhm:.2f}, threshold: {self.threshold:.2f})"

def find_peaks_1d(array, axis=None, **kwargs):
    if axis == 1:
        array = array.transpose()
    peaks, props = signal.find_peaks(array.flatten(), **kwargs)
    peaks = np.unravel_index(peaks, array.shape)
    if axis == 1:
        peaks = peaks[1], peaks[0]

    return peaks

def find_peaks_2d(array, **kwargs):
    peaks_x = find_peaks_1d(array=array, axis=0, **kwargs)
    peaks_y = find_peaks_1d(array=array, axis=1, **kwargs)

    matches = []
    for pxx, pxy in zip(peaks_x[0], peaks_x[1]):
        good_x = pxx == peaks_y[0]
        good_y = pxy == peaks_y[1]
        matching = good_x & good_y
        if np.any(matching):
            matches.append([peaks_y[0][matching], peaks_y[1][matching]])

    matches = np.array(matches).transpose()[0]

    return matches

class ManualFinder(object):

    def __init__(self, radius=None, **kwargs):

        self.radius = radius
        if self.radius is None:
            self.radius = kwargs.get('fwhm', 20)

    def __call__(self, image):

        # Initialize table of identified peaks
        peaks_table = Table(names=['x', 'dx', 'y', 'dy', 'flux', 'dflux'])

        # Create plot and graphically select apertures
        plot = StarFinderPlot(image_data=image)
        logger.info("Select guesses for iterative source selection!")
        guesses = plot.select_apertures(marker_size=100)

        # Iterate through guesses and identify peaks within a radius from the guess
        for guess in guesses:
            peak = self.find_closest_peak(image=image, guess=guess, radius=self.radius)
            peaks_table.add_row([peak[1], None, peak[0], None, image[peak], None])

        return peaks_table

    @staticmethod
    def find_closest_peak(image, guess, radius, threshold=0.1):
        guess = Point(*guess, order='yx')
        peak = None

        done = False
        while not done:
            box = Box.centered_at(x0=guess.x, y0=guess.y, radius=radius)
            box.crop_to_shape(image.shape)
            aperture = box(image)
            peak = peak_index(aperture)
            peak = Point(*peak, order='yx')
            peak.y += box.y_min
            peak.x += box.x_min

            # Test for convergence
            if peak.distance_to(guess) < threshold:
                done = True
            else:
                guess = peak
        return peak.y, peak.x


from zquantum.core.interfaces.optimizer import Optimizer
from scipy.optimize import OptimizeResult
import cma
import copy

class CMAESOptimizer(Optimizer):

    def __init__(self, options):
        if "sigma_0" not in options.keys():
            raise RuntimeError("Error: CMAESOptimizer input options dictionary must contain \"sigma_0\" field")
        else:
            self.sigma_0 = options.pop('sigma_0')
        self.options = options

    def minimize(self, cost_function, initial_params):
        """Minimize using the Covariance Matrix Adaptation Evolution Strategy
        (CMA-ES).

        Args:
            cost_function: the function whose return value is to be minimized.
            initial_params (numpy.ndarray): initial guess for the ansatz parameters.

        Returns:
            tuple: A tuple containing an optimization results dict and a numpy array
                with the optimized parameters.
        """

        # Optimization Results Object
        history = []
        def wrapped_cost_function(params):

            value = cost_function(params)
            history.append({'params': params, 'value': value})
            print(f'Function evaluation {len(history)}: {value}', flush=True)
            print(f'{params}', flush=True)

            return value

        strategy = cma.CMAEvolutionStrategy(initial_params, self.sigma_0, self.options)
        result = strategy.optimize(wrapped_cost_function).result

        optimization_results = {}
        optimization_results['opt_value'] = result.fbest
        optimization_results['opt_params'] = result.xbest
        optimization_results['history'] = history
        optimization_results['nfev'] = result.evaluations
        optimization_results['nit'] = result.iterations
        optimization_results['cma_xfavorite'] = list(result.xfavorite)

        return OptimizeResult(optimization_results)


import requests
import json
import sys
import urllib3
import time
from collections import defaultdict
import csv
import smtplib

def getUsers(server, admin, pw):
    user_list = []
    user_url = '{}/vmrest/users'.format(server)
    users = requests.get(user_url, auth=(admin, pw), verify=False,
        headers={'Accept': 'application/json'})

    if users.status_code == 200:
        users = users.json()
        for user in users['User']:
            user_list.append(user['Alias'])

        return user_list

    else:
        print(users.status_code)
        print(users.text)
        print("The status code of get users was not 200.")
        sys.exit(1)

def addIdentifyingInfo(id, server, admin, pw, user_dict):
    search_url = '{}/vmrest/users?query=(alias is {})'.format(server, id)
    search = requests.get(search_url, auth=(admin, pw), verify=False,
        headers={'Accept': 'application/json'})

    if search.status_code == 200:
        search = search.json()

        if search['@total'] == '1':
            flag = True
            search_user = search['User']
            required_keys = ['ObjectId', 'FirstName', 'LastName']

            for key in required_keys:
                if key not in search_user.keys():
                    flag = False
                    break

            if flag:
                user_dict['obj_id'] = search_user['ObjectId']
                user_dict['first_name'] = search_user['FirstName']
                user_dict['last_name'] = search_user['LastName']

            return flag

        else:
            print('No user with that id was found.')
            sys.exit(1)

    else:
        print(search.status_code)
        print(search.text)
        print('The status code of the search was not 200.')
        sys.exit(1)

def addCUPIInfo(server, admin, pw, user_dict):
    cupi_url = '{}/vmrest/users/{}'.format(server, user_dict['obj_id'])
    cupi_info = requests.get(cupi_url, auth=(admin, pw), verify=False,
        headers={'Accept': 'application/json'})

    if cupi_info.status_code == 200:
        cupi_info = cupi_info.json()
        required_keys = ['Building', 'Manager', 'Alias', 'BillingId',
        'CreationTime', 'Department', 'DisplayName', 'EmailAddress',
        'EmployeeId', 'LdapType', 'LdapCcmUserId', 'SmtpAddress', 'Title',
        'DtmfAccessId']
        flag = True
        for key in required_keys:
            if key not in cupi_info.keys():
                flag = False
                break

        if flag:
            user_dict['building'] = cupi_info['Building']
            user_dict['manager'] = cupi_info['Manager']
            user_dict['alias'] = cupi_info['Alias']
            user_dict['billing_id'] = cupi_info['BillingId']
            user_dict['creation_time'] = cupi_info['CreationTime']
            user_dict['department'] = cupi_info['Department']
            user_dict['display_name'] = cupi_info['DisplayName']
            user_dict['email_address'] = cupi_info['EmailAddress']
            user_dict['employee_id'] = cupi_info['EmployeeId']
            user_dict['ldap_type'] = cupi_info['LdapType']
            user_dict['ldap_user_id'] = cupi_info['LdapCcmUserId']
            user_dict['smtp_address'] = cupi_info['SmtpAddress']
            user_dict['title'] = cupi_info['Title']
            user_dict['extension'] = cupi_info['DtmfAccessId']

        return flag

    else:
        print(cupi_info.status_code)
        print(cupi_info.text)
        print('The status code of the user info search was not 200.')
        print("user info search failed with {}".format(user_dict['first_name']))
        sys.exit(1)

def addCUMIInfo(server, admin, pw, user_dict):
    read_url = '{}/vmrest/mailbox/folders/inbox/messages?userobjectid={}&read=true'.format(server, user_dict['obj_id'])
    read_info = requests.get(read_url, auth=(admin, pw), verify=False,
        headers={'Accept': 'application/json'})

    if read_info.status_code == 200:
        read_info = read_info.json()
        if 'Message' in read_info.keys():
            read_messages = read_info['Message']
            user_dict['total_read_30days'] = count30Day(read_messages)
        else:
            user_dict['total_read_30days'] = 'n/a'

    else:
        print(read_info.status_code)
        print(read_info.text)
        print('The status code of the messages API call was not 200.')
        sys.exit(1)

    unread_url = '{}/vmrest/mailbox/folders/inbox/messages?userobjectid={}&read=false'.format(server, user_dict['obj_id'])
    unread = requests.get(unread_url, auth=(admin, pw), verify=False,
        headers={'Accept': 'application/json'})

    if unread.status_code == 200:
        unread = unread.json()
        user_dict['total_unread'] = unread['@total']
        if 'Message' in unread.keys():
            unread_messages = unread['Message']
            user_dict['oldest'] = getOldestMessageInDays(unread_messages)
        else:
            user_dict['oldest'] = 'n/a'

    else:
        print(unread.status_code)
        print(unread.text)
        print('The status code of the unread voicemail search was not 200.')
        sys.exit(1)

#return the age of the oldest unread message in days
def getOldestMessageInDays(messages):
    oldest = messages[0]['ArrivalTime']

    for message in messages:
        if message['ArrivalTime'] < oldest:
            oldest = message['ArrivalTime']

    oldest_age_days = int((time.mktime(time.localtime()) - (int(oldest) / 1000)) / 86400)

    return oldest_age_days

def count30Day(messages):
    count_30day = 0

    for message in messages:
        if (time.mktime(time.localtime()) - (int(message['ArrivalTime']) / 1000)) / 86400 <= 30:
            count_30day += 1

    return count_30day